Skip to content

Commit 3ddd1ac

Browse files
[refactor] Separate noise filtering and centralize noise file detection (#277)
1 parent c060c4f commit 3ddd1ac

File tree

7 files changed

+253
-10
lines changed

7 files changed

+253
-10
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Noise File Detection Specification
2+
3+
## Purpose
4+
5+
This document lists the known noise file names filtered by the client inventory pipeline and their platform origin.
6+
7+
The runtime source of truth is the embedded JSON resource:
8+
`src/ByteSync.Client/Services/Inventories/noise-files.json`.
9+
10+
## Known noise file names
11+
12+
| File name | Origin platform | Typical purpose |
13+
| --- | --- | --- |
14+
| `desktop.ini` | Windows | Folder customization metadata |
15+
| `thumbs.db` | Windows | Thumbnail cache |
16+
| `ehthumbs.db` | Windows | Media Center thumbnail cache |
17+
| `ehthumbs_vista.db` | Windows | Vista Media Center thumbnail cache |
18+
| `.desktop.ini` | Windows/Linux legacy compatibility | Legacy hidden variant |
19+
| `.thumbs.db` | Windows/Linux legacy compatibility | Legacy hidden variant |
20+
| `.DS_Store` | macOS | Finder metadata |
21+
| `.AppleDouble` | macOS | Resource fork metadata |
22+
| `.LSOverride` | macOS | Launch Services overrides |
23+
| `.Spotlight-V100` | macOS | Spotlight indexing data |
24+
| `.Trashes` | macOS | Trash metadata or folder marker |
25+
| `.fseventsd` | macOS | File system event metadata |
26+
| `.TemporaryItems` | macOS | Temporary items marker |
27+
| `.VolumeIcon.icns` | macOS | Custom volume icon |
28+
| `.directory` | Linux (KDE) | Directory display metadata |
29+
30+
## Matching behavior
31+
32+
- On Linux, matching is case-sensitive.
33+
- On non-Linux platforms, matching is case-insensitive.

src/ByteSync.Client/ByteSync.Client.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@
111111
</EmbeddedResource>
112112
<None Remove="local.settings.json"/>
113113
<EmbeddedResource Include="local.settings.json"/>
114+
<EmbeddedResource Include="Services\Inventories\noise-files.json"/>
114115
</ItemGroup>
115116
<ItemGroup>
116117
<UpToDateCheckInput Remove="Assets\Resources\Resources.en.resx"/>
@@ -258,4 +259,4 @@
258259
<SubType>Code</SubType>
259260
</Compile>
260261
</ItemGroup>
261-
</Project>
262+
</Project>

src/ByteSync.Client/Services/Inventories/FileSystemInspector.cs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,7 @@ public bool IsSystemAttribute(FileInfo fileInfo)
6363

6464
public bool IsNoiseFileName(FileInfo fileInfo, OSPlatforms os)
6565
{
66-
var comparison = os == OSPlatforms.Linux ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
67-
68-
return fileInfo.Name.Equals("desktop.ini", comparison)
69-
|| fileInfo.Name.Equals("thumbs.db", comparison)
70-
|| fileInfo.Name.Equals(".desktop.ini", comparison)
71-
|| fileInfo.Name.Equals(".thumbs.db", comparison)
72-
|| fileInfo.Name.Equals(".DS_Store", comparison);
66+
return NoiseFileDetector.IsNoiseFileName(fileInfo.Name, os);
7367
}
7468

7569
public bool IsReparsePoint(FileSystemInfo fsi)
@@ -120,4 +114,4 @@ private bool SafeIsReparsePoint(FileSystemInfo fsi)
120114
return false;
121115
}
122116
}
123-
}
117+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using System.Reflection;
2+
using System.Text.Json;
3+
using ByteSync.Common.Business.Misc;
4+
5+
namespace ByteSync.Services.Inventories;
6+
7+
public static class NoiseFileDetector
8+
{
9+
private const string NoiseFileResourceSuffix = ".Services.Inventories.noise-files.json";
10+
private static readonly string[] KnownNoiseFileNames = LoadNoiseFileNames();
11+
12+
private static readonly HashSet<string> CaseSensitiveNoiseFileNames = new(KnownNoiseFileNames, StringComparer.Ordinal);
13+
private static readonly HashSet<string> CaseInsensitiveNoiseFileNames = new(KnownNoiseFileNames, StringComparer.OrdinalIgnoreCase);
14+
15+
public static bool IsNoiseFileName(string? fileName, OSPlatforms os)
16+
{
17+
if (string.IsNullOrWhiteSpace(fileName))
18+
{
19+
return false;
20+
}
21+
22+
return os == OSPlatforms.Linux
23+
? CaseSensitiveNoiseFileNames.Contains(fileName)
24+
: CaseInsensitiveNoiseFileNames.Contains(fileName);
25+
}
26+
27+
private static string[] LoadNoiseFileNames()
28+
{
29+
var assembly = typeof(NoiseFileDetector).Assembly;
30+
var resourceName = assembly.GetManifestResourceNames()
31+
.Single(rn => rn.EndsWith(NoiseFileResourceSuffix, StringComparison.Ordinal));
32+
33+
using var stream = assembly.GetManifestResourceStream(resourceName);
34+
ArgumentNullException.ThrowIfNull(stream);
35+
36+
var parsed = JsonSerializer.Deserialize<string[]>(stream);
37+
ArgumentNullException.ThrowIfNull(parsed);
38+
39+
return parsed
40+
.Where(s => !string.IsNullOrWhiteSpace(s))
41+
.Distinct(StringComparer.Ordinal)
42+
.ToArray();
43+
}
44+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[
2+
"desktop.ini",
3+
"thumbs.db",
4+
"ehthumbs.db",
5+
"ehthumbs_vista.db",
6+
".desktop.ini",
7+
".thumbs.db",
8+
".DS_Store",
9+
".AppleDouble",
10+
".LSOverride",
11+
".Spotlight-V100",
12+
".Trashes",
13+
".fseventsd",
14+
".TemporaryItems",
15+
".VolumeIcon.icns",
16+
".directory"
17+
]

tests/ByteSync.Client.UnitTests/Services/Inventories/FileSystemInspectorTests.cs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using ByteSync.Business.Inventories;
2+
using ByteSync.Common.Business.Misc;
23
using ByteSync.Interfaces.Controls.Inventories;
34
using ByteSync.Services.Inventories;
45
using FluentAssertions;
@@ -131,4 +132,46 @@ public void ClassifyEntry_FallsBackToRegularFile_WhenPosixClassifierThrows()
131132
Directory.Delete(tempDirectory.FullName, true);
132133
}
133134
}
134-
}
135+
136+
[Test]
137+
public void IsNoiseFileName_ShouldReturnTrue_ForKnownNoiseFile()
138+
{
139+
var inspector = new FileSystemInspector();
140+
var tempDirectory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")));
141+
var filePath = Path.Combine(tempDirectory.FullName, "thumbs.db");
142+
File.WriteAllText(filePath, "x");
143+
var fileInfo = new FileInfo(filePath);
144+
145+
try
146+
{
147+
var result = inspector.IsNoiseFileName(fileInfo, OSPlatforms.Windows);
148+
149+
result.Should().BeTrue();
150+
}
151+
finally
152+
{
153+
Directory.Delete(tempDirectory.FullName, true);
154+
}
155+
}
156+
157+
[Test]
158+
public void IsNoiseFileName_ShouldReturnFalse_ForUnknownFile()
159+
{
160+
var inspector = new FileSystemInspector();
161+
var tempDirectory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")));
162+
var filePath = Path.Combine(tempDirectory.FullName, "regular.txt");
163+
File.WriteAllText(filePath, "x");
164+
var fileInfo = new FileInfo(filePath);
165+
166+
try
167+
{
168+
var result = inspector.IsNoiseFileName(fileInfo, OSPlatforms.Windows);
169+
170+
result.Should().BeFalse();
171+
}
172+
finally
173+
{
174+
Directory.Delete(tempDirectory.FullName, true);
175+
}
176+
}
177+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
using System.Text.Json;
2+
using ByteSync.Common.Business.Misc;
3+
using ByteSync.Services.Inventories;
4+
using FluentAssertions;
5+
using NUnit.Framework;
6+
7+
namespace ByteSync.Client.UnitTests.Services.Inventories;
8+
9+
public class NoiseFileDetectorTests
10+
{
11+
private static readonly string[] KnownNoiseFileNames = LoadNoiseFileNamesFromEmbeddedResource();
12+
13+
[TestCaseSource(nameof(KnownNoiseFileNames))]
14+
public void IsNoiseFileName_ShouldReturnTrue_ForKnownNoiseFiles_OnWindows(string fileName)
15+
{
16+
var result = NoiseFileDetector.IsNoiseFileName(fileName, OSPlatforms.Windows);
17+
18+
result.Should().BeTrue();
19+
}
20+
21+
[TestCaseSource(nameof(KnownNoiseFileNames))]
22+
public void IsNoiseFileName_ShouldReturnTrue_ForKnownNoiseFiles_OnLinux(string fileName)
23+
{
24+
var result = NoiseFileDetector.IsNoiseFileName(fileName, OSPlatforms.Linux);
25+
26+
result.Should().BeTrue();
27+
}
28+
29+
[TestCase("DESKTOP.INI")]
30+
[TestCase("THUMBS.DB")]
31+
[TestCase("EHTHUMBS.DB")]
32+
[TestCase("EHTHUMBS_VISTA.DB")]
33+
[TestCase(".ds_store")]
34+
[TestCase(".appledouble")]
35+
[TestCase(".lsoverride")]
36+
[TestCase(".spotlight-v100")]
37+
[TestCase(".trashes")]
38+
[TestCase(".FSEVENTSD")]
39+
[TestCase(".temporaryitems")]
40+
[TestCase(".volumeicon.icns")]
41+
[TestCase(".DIRECTORY")]
42+
public void IsNoiseFileName_ShouldBeCaseInsensitive_OnNonLinuxPlatforms(string fileName)
43+
{
44+
var windowsResult = NoiseFileDetector.IsNoiseFileName(fileName, OSPlatforms.Windows);
45+
var macResult = NoiseFileDetector.IsNoiseFileName(fileName, OSPlatforms.MacOs);
46+
47+
windowsResult.Should().BeTrue();
48+
macResult.Should().BeTrue();
49+
}
50+
51+
[TestCase("DESKTOP.INI")]
52+
[TestCase("THUMBS.DB")]
53+
[TestCase("EHTHUMBS.DB")]
54+
[TestCase("EHTHUMBS_VISTA.DB")]
55+
[TestCase(".ds_store")]
56+
[TestCase(".appledouble")]
57+
[TestCase(".lsoverride")]
58+
[TestCase(".spotlight-v100")]
59+
[TestCase(".trashes")]
60+
[TestCase(".FSEVENTSD")]
61+
[TestCase(".temporaryitems")]
62+
[TestCase(".volumeicon.icns")]
63+
[TestCase(".DIRECTORY")]
64+
public void IsNoiseFileName_ShouldBeCaseSensitive_OnLinux(string fileName)
65+
{
66+
var result = NoiseFileDetector.IsNoiseFileName(fileName, OSPlatforms.Linux);
67+
68+
result.Should().BeFalse();
69+
}
70+
71+
[TestCase("readme.md")]
72+
[TestCase("normal.txt")]
73+
[TestCase(".gitignore")]
74+
public void IsNoiseFileName_ShouldReturnFalse_ForUnknownFileNames(string fileName)
75+
{
76+
var windowsResult = NoiseFileDetector.IsNoiseFileName(fileName, OSPlatforms.Windows);
77+
var linuxResult = NoiseFileDetector.IsNoiseFileName(fileName, OSPlatforms.Linux);
78+
79+
windowsResult.Should().BeFalse();
80+
linuxResult.Should().BeFalse();
81+
}
82+
83+
[TestCase(null)]
84+
[TestCase("")]
85+
[TestCase(" ")]
86+
public void IsNoiseFileName_ShouldReturnFalse_ForEmptyValues(string? fileName)
87+
{
88+
var windowsResult = NoiseFileDetector.IsNoiseFileName(fileName, OSPlatforms.Windows);
89+
var linuxResult = NoiseFileDetector.IsNoiseFileName(fileName, OSPlatforms.Linux);
90+
91+
windowsResult.Should().BeFalse();
92+
linuxResult.Should().BeFalse();
93+
}
94+
95+
private static string[] LoadNoiseFileNamesFromEmbeddedResource()
96+
{
97+
var assembly = typeof(NoiseFileDetector).Assembly;
98+
var resourceName = assembly.GetManifestResourceNames()
99+
.SingleOrDefault(rn => rn.EndsWith(".Services.Inventories.noise-files.json", StringComparison.Ordinal));
100+
101+
resourceName.Should().NotBeNull();
102+
103+
using var stream = assembly.GetManifestResourceStream(resourceName!);
104+
stream.Should().NotBeNull();
105+
106+
var data = JsonSerializer.Deserialize<string[]>(stream!);
107+
data.Should().NotBeNull();
108+
109+
return data!;
110+
}
111+
}

0 commit comments

Comments
 (0)