From c70f98516c93f2d697ce6ccdcd94f93a5a49aaa3 Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Wed, 11 Feb 2026 10:18:50 +0100 Subject: [PATCH 1/7] feature: extend noise entries and support noise directories --- .../Models/Inventories/SkipReason.cs | 2 +- .../Services/Inventories/InventoryBuilder.cs | 28 +++++++++++++++++-- .../Services/Inventories/noise-files.json | 3 ++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/ByteSync.Client/Models/Inventories/SkipReason.cs b/src/ByteSync.Client/Models/Inventories/SkipReason.cs index d065e5c9..449b1e91 100644 --- a/src/ByteSync.Client/Models/Inventories/SkipReason.cs +++ b/src/ByteSync.Client/Models/Inventories/SkipReason.cs @@ -5,7 +5,7 @@ public enum SkipReason Unknown = 0, Hidden = 1, SystemAttribute = 2, - NoiseFile = 3, + NoiseEntry = 3, Symlink = 4, SpecialPosixFile = 5, Offline = 6, diff --git a/src/ByteSync.Client/Services/Inventories/InventoryBuilder.cs b/src/ByteSync.Client/Services/Inventories/InventoryBuilder.cs index cd673e30..a484291e 100644 --- a/src/ByteSync.Client/Services/Inventories/InventoryBuilder.cs +++ b/src/ByteSync.Client/Services/Inventories/InventoryBuilder.cs @@ -487,6 +487,13 @@ private void DoAnalyze(InventoryPart inventoryPart, DirectoryInfo directoryInfo, return; } + if (!IsRootPath(inventoryPart, directoryInfo) && ShouldIgnoreNoiseDirectory(directoryInfo)) + { + RecordSkippedEntry(inventoryPart, directoryInfo, SkipReason.NoiseEntry, FileSystemEntryKind.Directory); + + return; + } + var directoryDescription = IdentityBuilder.BuildDirectoryDescription(inventoryPart, directoryInfo); AddFileSystemDescription(inventoryPart, directoryDescription); @@ -535,6 +542,23 @@ private bool ShouldIgnoreHiddenFile(FileInfo fileInfo) return false; } + private bool ShouldIgnoreNoiseDirectory(DirectoryInfo directoryInfo) + { + if (!IgnoreSystem) + { + return false; + } + + if (NoiseFileDetector.IsNoiseFileName(directoryInfo.Name, OSPlatform)) + { + _logger.LogInformation("Directory {Directory} is ignored because considered as noise", directoryInfo.FullName); + + return true; + } + + return false; + } + private SkipReason? GetSystemSkipReason(FileInfo fileInfo) { if (!IgnoreSystem) @@ -546,7 +570,7 @@ private bool ShouldIgnoreHiddenFile(FileInfo fileInfo) { _logger.LogInformation("File {File} is ignored because considered as noise", fileInfo.FullName); - return SkipReason.NoiseFile; + return SkipReason.NoiseEntry; } if (FileSystemInspector.IsSystemAttribute(fileInfo)) @@ -705,4 +729,4 @@ private void AddFileSystemDescription(InventoryPart inventoryPart, FileSystemDes } } } -} \ No newline at end of file +} diff --git a/src/ByteSync.Client/Services/Inventories/noise-files.json b/src/ByteSync.Client/Services/Inventories/noise-files.json index 22093319..008eadb1 100644 --- a/src/ByteSync.Client/Services/Inventories/noise-files.json +++ b/src/ByteSync.Client/Services/Inventories/noise-files.json @@ -3,10 +3,13 @@ "thumbs.db", "ehthumbs.db", "ehthumbs_vista.db", + "$RECYCLE.BIN", ".desktop.ini", ".thumbs.db", ".DS_Store", ".AppleDouble", + ".AppleDB", + ".AppleDesktop", ".LSOverride", ".Spotlight-V100", ".Trashes", From b1a02ac697ec02aa676e6a880f8fee61c21d00e3 Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Wed, 11 Feb 2026 10:18:58 +0100 Subject: [PATCH 2/7] test: cover noise directory behavior and new entries --- docs/specs/SPEC-noise-file-detection.md | 4 ++ .../InventoryBuilderInspectorTests.cs | 65 ++++++++++++++++++- .../Inventories/NoiseFileDetectorTests.cs | 8 ++- 3 files changed, 75 insertions(+), 2 deletions(-) diff --git a/docs/specs/SPEC-noise-file-detection.md b/docs/specs/SPEC-noise-file-detection.md index 0d56cabf..5ac34fa8 100644 --- a/docs/specs/SPEC-noise-file-detection.md +++ b/docs/specs/SPEC-noise-file-detection.md @@ -15,10 +15,13 @@ The runtime source of truth is the embedded JSON resource: | `thumbs.db` | Windows | Thumbnail cache | | `ehthumbs.db` | Windows | Media Center thumbnail cache | | `ehthumbs_vista.db` | Windows | Vista Media Center thumbnail cache | +| `$RECYCLE.BIN` | Windows | Recycle bin folder marker | | `.desktop.ini` | Windows/Linux legacy compatibility | Legacy hidden variant | | `.thumbs.db` | Windows/Linux legacy compatibility | Legacy hidden variant | | `.DS_Store` | macOS | Finder metadata | | `.AppleDouble` | macOS | Resource fork metadata | +| `.AppleDB` | macOS | Apple database file | +| `.AppleDesktop` | macOS | Apple desktop database file | | `.LSOverride` | macOS | Launch Services overrides | | `.Spotlight-V100` | macOS | Spotlight indexing data | | `.Trashes` | macOS | Trash metadata or folder marker | @@ -31,3 +34,4 @@ The runtime source of truth is the embedded JSON resource: - On Linux, matching is case-sensitive. - On non-Linux platforms, matching is case-insensitive. +- macOS remains intentionally case-insensitive for consistency with current product behavior. diff --git a/tests/ByteSync.Client.UnitTests/Services/Inventories/InventoryBuilderInspectorTests.cs b/tests/ByteSync.Client.UnitTests/Services/Inventories/InventoryBuilderInspectorTests.cs index 3ac31b4c..d66370b7 100644 --- a/tests/ByteSync.Client.UnitTests/Services/Inventories/InventoryBuilderInspectorTests.cs +++ b/tests/ByteSync.Client.UnitTests/Services/Inventories/InventoryBuilderInspectorTests.cs @@ -265,7 +265,70 @@ public async Task Noise_Child_File_Is_Recorded() await builder.BuildBaseInventoryAsync(invPath); processData.SkippedEntries.Should() - .ContainSingle(e => e.Name == "thumbs.db" && e.Reason == SkipReason.NoiseFile); + .ContainSingle(e => e.Name == "thumbs.db" && e.Reason == SkipReason.NoiseEntry); + } + + [Test] + public async Task Noise_Child_Directory_Is_Recorded_And_Not_Traversed() + { + var insp = new Mock(MockBehavior.Strict); + SetupDefaultClassification(insp); + insp.Setup(i => i.IsHidden(It.IsAny(), It.IsAny())).Returns(false); + insp.Setup(i => i.IsHidden(It.IsAny(), It.IsAny())).Returns(false); + insp.Setup(i => i.IsNoiseFileName(It.IsAny(), It.IsAny())).Returns(false); + insp.Setup(i => i.IsSystemAttribute(It.IsAny())).Returns(false); + insp.Setup(i => i.IsReparsePoint(It.IsAny())).Returns(false); + insp.Setup(i => i.Exists(It.IsAny())).Returns(true); + insp.Setup(i => i.IsOffline(It.IsAny())).Returns(false); + insp.Setup(i => i.IsRecallOnDataAccess(It.IsAny())).Returns(false); + var (builder, processData) = CreateBuilderWithData(insp.Object); + + var root = Directory.CreateDirectory(Path.Combine(TestDirectory.FullName, "root_noise_dir")); + var visiblePath = Path.Combine(root.FullName, "visible.txt"); + await File.WriteAllTextAsync(visiblePath, "x"); + + var noiseDirectory = Directory.CreateDirectory(Path.Combine(root.FullName, "$RECYCLE.BIN")); + var nestedNoiseFile = Path.Combine(noiseDirectory.FullName, "nested.txt"); + await File.WriteAllTextAsync(nestedNoiseFile, "x"); + + builder.AddInventoryPart(root.FullName); + var invPath = Path.Combine(TestDirectory.FullName, "inv_noise_dir.zip"); + await builder.BuildBaseInventoryAsync(invPath); + + var part = builder.Inventory.InventoryParts.Single(); + part.FileDescriptions.Should().ContainSingle(fd => fd.Name == "visible.txt"); + part.FileDescriptions.Should().NotContain(fd => fd.Name == "nested.txt"); + + processData.SkippedEntries.Should() + .ContainSingle(e => e.Name == "$RECYCLE.BIN" && e.Reason == SkipReason.NoiseEntry); + } + + [Test] + public async Task Noise_Root_Directory_Is_Analyzed() + { + var insp = new Mock(MockBehavior.Strict); + SetupDefaultClassification(insp); + insp.Setup(i => i.IsHidden(It.IsAny(), It.IsAny())).Returns(false); + insp.Setup(i => i.IsHidden(It.IsAny(), It.IsAny())).Returns(false); + insp.Setup(i => i.IsNoiseFileName(It.IsAny(), It.IsAny())).Returns(false); + insp.Setup(i => i.IsSystemAttribute(It.IsAny())).Returns(false); + insp.Setup(i => i.IsReparsePoint(It.IsAny())).Returns(false); + insp.Setup(i => i.Exists(It.IsAny())).Returns(true); + insp.Setup(i => i.IsOffline(It.IsAny())).Returns(false); + insp.Setup(i => i.IsRecallOnDataAccess(It.IsAny())).Returns(false); + var (builder, processData) = CreateBuilderWithData(insp.Object); + + var noiseRoot = Directory.CreateDirectory(Path.Combine(TestDirectory.FullName, "$RECYCLE.BIN")); + var filePath = Path.Combine(noiseRoot.FullName, "inside.txt"); + await File.WriteAllTextAsync(filePath, "x"); + + builder.AddInventoryPart(noiseRoot.FullName); + var invPath = Path.Combine(TestDirectory.FullName, "inv_noise_root_dir.zip"); + await builder.BuildBaseInventoryAsync(invPath); + + var part = builder.Inventory.InventoryParts.Single(); + part.FileDescriptions.Should().ContainSingle(fd => fd.Name == "inside.txt"); + processData.SkippedEntries.Should().NotContain(e => e.Name == "$RECYCLE.BIN"); } [Test] diff --git a/tests/ByteSync.Client.UnitTests/Services/Inventories/NoiseFileDetectorTests.cs b/tests/ByteSync.Client.UnitTests/Services/Inventories/NoiseFileDetectorTests.cs index 80fb9d1e..21464abb 100644 --- a/tests/ByteSync.Client.UnitTests/Services/Inventories/NoiseFileDetectorTests.cs +++ b/tests/ByteSync.Client.UnitTests/Services/Inventories/NoiseFileDetectorTests.cs @@ -30,8 +30,11 @@ public void IsNoiseFileName_ShouldReturnTrue_ForKnownNoiseFiles_OnLinux(string f [TestCase("THUMBS.DB")] [TestCase("EHTHUMBS.DB")] [TestCase("EHTHUMBS_VISTA.DB")] + [TestCase("$recycle.bin")] [TestCase(".ds_store")] [TestCase(".appledouble")] + [TestCase(".appledb")] + [TestCase(".appledesktop")] [TestCase(".lsoverride")] [TestCase(".spotlight-v100")] [TestCase(".trashes")] @@ -52,8 +55,11 @@ public void IsNoiseFileName_ShouldBeCaseInsensitive_OnNonLinuxPlatforms(string f [TestCase("THUMBS.DB")] [TestCase("EHTHUMBS.DB")] [TestCase("EHTHUMBS_VISTA.DB")] + [TestCase("$recycle.bin")] [TestCase(".ds_store")] [TestCase(".appledouble")] + [TestCase(".appledb")] + [TestCase(".appledesktop")] [TestCase(".lsoverride")] [TestCase(".spotlight-v100")] [TestCase(".trashes")] @@ -108,4 +114,4 @@ private static string[] LoadNoiseFileNamesFromEmbeddedResource() return data!; } -} \ No newline at end of file +} From 56b9f332d2e05c485b5de5fa01031441a34c9f54 Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Wed, 11 Feb 2026 10:59:21 +0100 Subject: [PATCH 3/7] refactor: route noise filtering through filesystem inspector --- .../Inventories/IFileSystemInspector.cs | 4 +++- .../Inventories/FileSystemInspector.cs | 7 ++++++- .../Services/Inventories/InventoryBuilder.cs | 4 ++-- .../Inventories/FileSystemInspectorTests.cs | 20 +++++++++++++++++++ .../InventoryBuilderInspectorTests.cs | 7 +++++++ 5 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/ByteSync.Client/Interfaces/Controls/Inventories/IFileSystemInspector.cs b/src/ByteSync.Client/Interfaces/Controls/Inventories/IFileSystemInspector.cs index bd08696d..bbb2f82f 100644 --- a/src/ByteSync.Client/Interfaces/Controls/Inventories/IFileSystemInspector.cs +++ b/src/ByteSync.Client/Interfaces/Controls/Inventories/IFileSystemInspector.cs @@ -12,6 +12,8 @@ public interface IFileSystemInspector bool IsSystemAttribute(FileInfo fileInfo); + bool IsNoiseEntryName(string? entryName, OSPlatforms os); + bool IsNoiseFileName(FileInfo fileInfo, OSPlatforms os); bool IsReparsePoint(FileSystemInfo fsi); @@ -21,4 +23,4 @@ public interface IFileSystemInspector bool IsOffline(FileInfo fileInfo); bool IsRecallOnDataAccess(FileInfo fileInfo); -} \ No newline at end of file +} diff --git a/src/ByteSync.Client/Services/Inventories/FileSystemInspector.cs b/src/ByteSync.Client/Services/Inventories/FileSystemInspector.cs index 47e33b74..3d4d274f 100644 --- a/src/ByteSync.Client/Services/Inventories/FileSystemInspector.cs +++ b/src/ByteSync.Client/Services/Inventories/FileSystemInspector.cs @@ -61,9 +61,14 @@ public bool IsSystemAttribute(FileInfo fileInfo) return isSystem; } + public bool IsNoiseEntryName(string? entryName, OSPlatforms os) + { + return NoiseFileDetector.IsNoiseFileName(entryName, os); + } + public bool IsNoiseFileName(FileInfo fileInfo, OSPlatforms os) { - return NoiseFileDetector.IsNoiseFileName(fileInfo.Name, os); + return IsNoiseEntryName(fileInfo.Name, os); } public bool IsReparsePoint(FileSystemInfo fsi) diff --git a/src/ByteSync.Client/Services/Inventories/InventoryBuilder.cs b/src/ByteSync.Client/Services/Inventories/InventoryBuilder.cs index a484291e..437e17a3 100644 --- a/src/ByteSync.Client/Services/Inventories/InventoryBuilder.cs +++ b/src/ByteSync.Client/Services/Inventories/InventoryBuilder.cs @@ -549,7 +549,7 @@ private bool ShouldIgnoreNoiseDirectory(DirectoryInfo directoryInfo) return false; } - if (NoiseFileDetector.IsNoiseFileName(directoryInfo.Name, OSPlatform)) + if (FileSystemInspector.IsNoiseEntryName(directoryInfo.Name, OSPlatform)) { _logger.LogInformation("Directory {Directory} is ignored because considered as noise", directoryInfo.FullName); @@ -566,7 +566,7 @@ private bool ShouldIgnoreNoiseDirectory(DirectoryInfo directoryInfo) return null; } - if (FileSystemInspector.IsNoiseFileName(fileInfo, OSPlatform)) + if (FileSystemInspector.IsNoiseEntryName(fileInfo.Name, OSPlatform)) { _logger.LogInformation("File {File} is ignored because considered as noise", fileInfo.FullName); diff --git a/tests/ByteSync.Client.UnitTests/Services/Inventories/FileSystemInspectorTests.cs b/tests/ByteSync.Client.UnitTests/Services/Inventories/FileSystemInspectorTests.cs index f69d72c2..dc685803 100644 --- a/tests/ByteSync.Client.UnitTests/Services/Inventories/FileSystemInspectorTests.cs +++ b/tests/ByteSync.Client.UnitTests/Services/Inventories/FileSystemInspectorTests.cs @@ -133,6 +133,26 @@ public void ClassifyEntry_FallsBackToRegularFile_WhenPosixClassifierThrows() } } + [Test] + public void IsNoiseEntryName_ShouldReturnTrue_ForKnownNoiseEntry() + { + var inspector = new FileSystemInspector(); + + var result = inspector.IsNoiseEntryName("thumbs.db", OSPlatforms.Windows); + + result.Should().BeTrue(); + } + + [Test] + public void IsNoiseEntryName_ShouldReturnFalse_ForUnknownEntry() + { + var inspector = new FileSystemInspector(); + + var result = inspector.IsNoiseEntryName("regular.txt", OSPlatforms.Windows); + + result.Should().BeFalse(); + } + [Test] public void IsNoiseFileName_ShouldReturnTrue_ForKnownNoiseFile() { diff --git a/tests/ByteSync.Client.UnitTests/Services/Inventories/InventoryBuilderInspectorTests.cs b/tests/ByteSync.Client.UnitTests/Services/Inventories/InventoryBuilderInspectorTests.cs index d66370b7..592cba11 100644 --- a/tests/ByteSync.Client.UnitTests/Services/Inventories/InventoryBuilderInspectorTests.cs +++ b/tests/ByteSync.Client.UnitTests/Services/Inventories/InventoryBuilderInspectorTests.cs @@ -94,6 +94,9 @@ private static void SetupDefaultClassification(Mock inspec FileInfo => FileSystemEntryKind.RegularFile, _ => FileSystemEntryKind.Unknown }); + inspector + .Setup(i => i.IsNoiseEntryName(It.IsAny(), It.IsAny())) + .Returns(false); } [Test] @@ -243,6 +246,8 @@ public async Task Noise_Child_File_Is_Recorded() SetupDefaultClassification(insp); insp.Setup(i => i.IsHidden(It.IsAny(), It.IsAny())).Returns(false); insp.Setup(i => i.IsHidden(It.IsAny(), It.IsAny())).Returns(false); + insp.Setup(i => i.IsNoiseEntryName(It.Is(name => name == "thumbs.db"), It.IsAny())) + .Returns(true); insp.Setup(i => i.IsNoiseFileName(It.Is(fi => fi.Name == "thumbs.db"), It.IsAny())) .Returns(true); insp.Setup(i => i.IsNoiseFileName(It.Is(fi => fi.Name != "thumbs.db"), It.IsAny())) @@ -275,6 +280,8 @@ public async Task Noise_Child_Directory_Is_Recorded_And_Not_Traversed() SetupDefaultClassification(insp); insp.Setup(i => i.IsHidden(It.IsAny(), It.IsAny())).Returns(false); insp.Setup(i => i.IsHidden(It.IsAny(), It.IsAny())).Returns(false); + insp.Setup(i => i.IsNoiseEntryName(It.Is(name => name == "$RECYCLE.BIN"), It.IsAny())) + .Returns(true); insp.Setup(i => i.IsNoiseFileName(It.IsAny(), It.IsAny())).Returns(false); insp.Setup(i => i.IsSystemAttribute(It.IsAny())).Returns(false); insp.Setup(i => i.IsReparsePoint(It.IsAny())).Returns(false); From b89bd96f2d312218b4d9457635871554a6a42c8a Mon Sep 17 00:00:00 2001 From: Paul Fresquet <61119222+paul-fresquet@users.noreply.github.com> Date: Wed, 11 Feb 2026 11:27:53 +0100 Subject: [PATCH 4/7] Update docs/specs/SPEC-noise-file-detection.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/specs/SPEC-noise-file-detection.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/specs/SPEC-noise-file-detection.md b/docs/specs/SPEC-noise-file-detection.md index 5ac34fa8..2405a3d8 100644 --- a/docs/specs/SPEC-noise-file-detection.md +++ b/docs/specs/SPEC-noise-file-detection.md @@ -33,5 +33,4 @@ The runtime source of truth is the embedded JSON resource: ## Matching behavior - On Linux, matching is case-sensitive. -- On non-Linux platforms, matching is case-insensitive. -- macOS remains intentionally case-insensitive for consistency with current product behavior. +- On non-Linux platforms (including macOS), matching is case-insensitive; macOS remains intentionally case-insensitive for consistency with current product behavior and to reflect the deviation from the original issue wording. From a7e96162b350a0a016ae5c02d6d24dd98ed6e75e Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Wed, 11 Feb 2026 11:28:13 +0100 Subject: [PATCH 5/7] refactor: cleanup --- .../Interfaces/Controls/Inventories/IFileSystemInspector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ByteSync.Client/Interfaces/Controls/Inventories/IFileSystemInspector.cs b/src/ByteSync.Client/Interfaces/Controls/Inventories/IFileSystemInspector.cs index bbb2f82f..2b3b92a2 100644 --- a/src/ByteSync.Client/Interfaces/Controls/Inventories/IFileSystemInspector.cs +++ b/src/ByteSync.Client/Interfaces/Controls/Inventories/IFileSystemInspector.cs @@ -23,4 +23,4 @@ public interface IFileSystemInspector bool IsOffline(FileInfo fileInfo); bool IsRecallOnDataAccess(FileInfo fileInfo); -} +} \ No newline at end of file From 62f435a1f3f9edb6a6aa83c2d6793fb3f922947b Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Wed, 11 Feb 2026 11:34:22 +0100 Subject: [PATCH 6/7] refactor: split noise detection for files and directories --- .../Inventories/IFileSystemInspector.cs | 6 ++-- .../Inventories/FileSystemInspector.cs | 8 ++--- .../Services/Inventories/InventoryBuilder.cs | 4 +-- .../Inventories/FileSystemInspectorTests.cs | 30 +++++++++++++++---- .../InventoryBuilderInspectorTests.cs | 6 ++-- 5 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/ByteSync.Client/Interfaces/Controls/Inventories/IFileSystemInspector.cs b/src/ByteSync.Client/Interfaces/Controls/Inventories/IFileSystemInspector.cs index 2b3b92a2..3fbd3312 100644 --- a/src/ByteSync.Client/Interfaces/Controls/Inventories/IFileSystemInspector.cs +++ b/src/ByteSync.Client/Interfaces/Controls/Inventories/IFileSystemInspector.cs @@ -12,10 +12,10 @@ public interface IFileSystemInspector bool IsSystemAttribute(FileInfo fileInfo); - bool IsNoiseEntryName(string? entryName, OSPlatforms os); - bool IsNoiseFileName(FileInfo fileInfo, OSPlatforms os); + bool IsNoiseDirectoryName(DirectoryInfo directoryInfo, OSPlatforms os); + bool IsReparsePoint(FileSystemInfo fsi); bool Exists(FileInfo fileInfo); @@ -23,4 +23,4 @@ public interface IFileSystemInspector bool IsOffline(FileInfo fileInfo); bool IsRecallOnDataAccess(FileInfo fileInfo); -} \ No newline at end of file +} diff --git a/src/ByteSync.Client/Services/Inventories/FileSystemInspector.cs b/src/ByteSync.Client/Services/Inventories/FileSystemInspector.cs index 3d4d274f..911d149a 100644 --- a/src/ByteSync.Client/Services/Inventories/FileSystemInspector.cs +++ b/src/ByteSync.Client/Services/Inventories/FileSystemInspector.cs @@ -61,14 +61,14 @@ public bool IsSystemAttribute(FileInfo fileInfo) return isSystem; } - public bool IsNoiseEntryName(string? entryName, OSPlatforms os) + public bool IsNoiseFileName(FileInfo fileInfo, OSPlatforms os) { - return NoiseFileDetector.IsNoiseFileName(entryName, os); + return NoiseFileDetector.IsNoiseFileName(fileInfo.Name, os); } - public bool IsNoiseFileName(FileInfo fileInfo, OSPlatforms os) + public bool IsNoiseDirectoryName(DirectoryInfo directoryInfo, OSPlatforms os) { - return IsNoiseEntryName(fileInfo.Name, os); + return NoiseFileDetector.IsNoiseFileName(directoryInfo.Name, os); } public bool IsReparsePoint(FileSystemInfo fsi) diff --git a/src/ByteSync.Client/Services/Inventories/InventoryBuilder.cs b/src/ByteSync.Client/Services/Inventories/InventoryBuilder.cs index 437e17a3..ade6a530 100644 --- a/src/ByteSync.Client/Services/Inventories/InventoryBuilder.cs +++ b/src/ByteSync.Client/Services/Inventories/InventoryBuilder.cs @@ -549,7 +549,7 @@ private bool ShouldIgnoreNoiseDirectory(DirectoryInfo directoryInfo) return false; } - if (FileSystemInspector.IsNoiseEntryName(directoryInfo.Name, OSPlatform)) + if (FileSystemInspector.IsNoiseDirectoryName(directoryInfo, OSPlatform)) { _logger.LogInformation("Directory {Directory} is ignored because considered as noise", directoryInfo.FullName); @@ -566,7 +566,7 @@ private bool ShouldIgnoreNoiseDirectory(DirectoryInfo directoryInfo) return null; } - if (FileSystemInspector.IsNoiseEntryName(fileInfo.Name, OSPlatform)) + if (FileSystemInspector.IsNoiseFileName(fileInfo, OSPlatform)) { _logger.LogInformation("File {File} is ignored because considered as noise", fileInfo.FullName); diff --git a/tests/ByteSync.Client.UnitTests/Services/Inventories/FileSystemInspectorTests.cs b/tests/ByteSync.Client.UnitTests/Services/Inventories/FileSystemInspectorTests.cs index dc685803..6a61e25c 100644 --- a/tests/ByteSync.Client.UnitTests/Services/Inventories/FileSystemInspectorTests.cs +++ b/tests/ByteSync.Client.UnitTests/Services/Inventories/FileSystemInspectorTests.cs @@ -134,23 +134,41 @@ public void ClassifyEntry_FallsBackToRegularFile_WhenPosixClassifierThrows() } [Test] - public void IsNoiseEntryName_ShouldReturnTrue_ForKnownNoiseEntry() + public void IsNoiseDirectoryName_ShouldReturnTrue_ForKnownNoiseDirectory() { var inspector = new FileSystemInspector(); + var tempDirectory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"))); + var noiseDirectory = Directory.CreateDirectory(Path.Combine(tempDirectory.FullName, "$RECYCLE.BIN")); - var result = inspector.IsNoiseEntryName("thumbs.db", OSPlatforms.Windows); + try + { + var result = inspector.IsNoiseDirectoryName(noiseDirectory, OSPlatforms.Windows); - result.Should().BeTrue(); + result.Should().BeTrue(); + } + finally + { + Directory.Delete(tempDirectory.FullName, true); + } } [Test] - public void IsNoiseEntryName_ShouldReturnFalse_ForUnknownEntry() + public void IsNoiseDirectoryName_ShouldReturnFalse_ForUnknownDirectory() { var inspector = new FileSystemInspector(); + var tempDirectory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"))); + var regularDirectory = Directory.CreateDirectory(Path.Combine(tempDirectory.FullName, "regular")); - var result = inspector.IsNoiseEntryName("regular.txt", OSPlatforms.Windows); + try + { + var result = inspector.IsNoiseDirectoryName(regularDirectory, OSPlatforms.Windows); - result.Should().BeFalse(); + result.Should().BeFalse(); + } + finally + { + Directory.Delete(tempDirectory.FullName, true); + } } [Test] diff --git a/tests/ByteSync.Client.UnitTests/Services/Inventories/InventoryBuilderInspectorTests.cs b/tests/ByteSync.Client.UnitTests/Services/Inventories/InventoryBuilderInspectorTests.cs index 592cba11..33d40338 100644 --- a/tests/ByteSync.Client.UnitTests/Services/Inventories/InventoryBuilderInspectorTests.cs +++ b/tests/ByteSync.Client.UnitTests/Services/Inventories/InventoryBuilderInspectorTests.cs @@ -95,7 +95,7 @@ private static void SetupDefaultClassification(Mock inspec _ => FileSystemEntryKind.Unknown }); inspector - .Setup(i => i.IsNoiseEntryName(It.IsAny(), It.IsAny())) + .Setup(i => i.IsNoiseDirectoryName(It.IsAny(), It.IsAny())) .Returns(false); } @@ -246,8 +246,6 @@ public async Task Noise_Child_File_Is_Recorded() SetupDefaultClassification(insp); insp.Setup(i => i.IsHidden(It.IsAny(), It.IsAny())).Returns(false); insp.Setup(i => i.IsHidden(It.IsAny(), It.IsAny())).Returns(false); - insp.Setup(i => i.IsNoiseEntryName(It.Is(name => name == "thumbs.db"), It.IsAny())) - .Returns(true); insp.Setup(i => i.IsNoiseFileName(It.Is(fi => fi.Name == "thumbs.db"), It.IsAny())) .Returns(true); insp.Setup(i => i.IsNoiseFileName(It.Is(fi => fi.Name != "thumbs.db"), It.IsAny())) @@ -280,7 +278,7 @@ public async Task Noise_Child_Directory_Is_Recorded_And_Not_Traversed() SetupDefaultClassification(insp); insp.Setup(i => i.IsHidden(It.IsAny(), It.IsAny())).Returns(false); insp.Setup(i => i.IsHidden(It.IsAny(), It.IsAny())).Returns(false); - insp.Setup(i => i.IsNoiseEntryName(It.Is(name => name == "$RECYCLE.BIN"), It.IsAny())) + insp.Setup(i => i.IsNoiseDirectoryName(It.Is(di => di.Name == "$RECYCLE.BIN"), It.IsAny())) .Returns(true); insp.Setup(i => i.IsNoiseFileName(It.IsAny(), It.IsAny())).Returns(false); insp.Setup(i => i.IsSystemAttribute(It.IsAny())).Returns(false); From 3a1cba4a9b13b6c0b750ee0c99a3bf6a9a5f46dc Mon Sep 17 00:00:00 2001 From: Paul Fresquet Date: Wed, 11 Feb 2026 11:42:51 +0100 Subject: [PATCH 7/7] refactor: cache root path check for directory analysis --- .../Services/Inventories/InventoryBuilder.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ByteSync.Client/Services/Inventories/InventoryBuilder.cs b/src/ByteSync.Client/Services/Inventories/InventoryBuilder.cs index ade6a530..3c03da85 100644 --- a/src/ByteSync.Client/Services/Inventories/InventoryBuilder.cs +++ b/src/ByteSync.Client/Services/Inventories/InventoryBuilder.cs @@ -480,14 +480,16 @@ private void DoAnalyze(InventoryPart inventoryPart, DirectoryInfo directoryInfo, return; } - if (!IsRootPath(inventoryPart, directoryInfo) && ShouldIgnoreHiddenDirectory(directoryInfo)) + var isRoot = IsRootPath(inventoryPart, directoryInfo); + + if (!isRoot && ShouldIgnoreHiddenDirectory(directoryInfo)) { RecordSkippedEntry(inventoryPart, directoryInfo, SkipReason.Hidden, FileSystemEntryKind.Directory); return; } - if (!IsRootPath(inventoryPart, directoryInfo) && ShouldIgnoreNoiseDirectory(directoryInfo)) + if (!isRoot && ShouldIgnoreNoiseDirectory(directoryInfo)) { RecordSkippedEntry(inventoryPart, directoryInfo, SkipReason.NoiseEntry, FileSystemEntryKind.Directory);