Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions docs/specs/SPEC-noise-file-detection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Noise File Detection Specification

## Purpose

This document lists the known noise file names filtered by the client inventory pipeline and their platform origin.

The runtime source of truth is the embedded JSON resource:
`src/ByteSync.Client/Services/Inventories/noise-files.json`.

## Known noise file names

| File name | Origin platform | Typical purpose |
| --- | --- | --- |
| `desktop.ini` | Windows | Folder customization metadata |
| `thumbs.db` | Windows | Thumbnail cache |
| `ehthumbs.db` | Windows | Media Center thumbnail cache |
| `ehthumbs_vista.db` | Windows | Vista Media Center thumbnail cache |
| `.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 |
| `.LSOverride` | macOS | Launch Services overrides |
| `.Spotlight-V100` | macOS | Spotlight indexing data |
| `.Trashes` | macOS | Trash metadata or folder marker |
| `.fseventsd` | macOS | File system event metadata |
| `.TemporaryItems` | macOS | Temporary items marker |
| `.VolumeIcon.icns` | macOS | Custom volume icon |
| `.directory` | Linux (KDE) | Directory display metadata |

## Matching behavior

- On Linux, matching is case-sensitive.
- On non-Linux platforms, matching is case-insensitive.
3 changes: 2 additions & 1 deletion src/ByteSync.Client/ByteSync.Client.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
</EmbeddedResource>
<None Remove="local.settings.json"/>
<EmbeddedResource Include="local.settings.json"/>
<EmbeddedResource Include="Services\Inventories\noise-files.json"/>
</ItemGroup>
<ItemGroup>
<UpToDateCheckInput Remove="Assets\Resources\Resources.en.resx"/>
Expand Down Expand Up @@ -258,4 +259,4 @@
<SubType>Code</SubType>
</Compile>
</ItemGroup>
</Project>
</Project>
10 changes: 2 additions & 8 deletions src/ByteSync.Client/Services/Inventories/FileSystemInspector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,7 @@ public bool IsSystemAttribute(FileInfo fileInfo)

public bool IsNoiseFileName(FileInfo fileInfo, OSPlatforms os)
{
var comparison = os == OSPlatforms.Linux ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;

return fileInfo.Name.Equals("desktop.ini", comparison)
|| fileInfo.Name.Equals("thumbs.db", comparison)
|| fileInfo.Name.Equals(".desktop.ini", comparison)
|| fileInfo.Name.Equals(".thumbs.db", comparison)
|| fileInfo.Name.Equals(".DS_Store", comparison);
return NoiseFileDetector.IsNoiseFileName(fileInfo.Name, os);
}

public bool IsReparsePoint(FileSystemInfo fsi)
Expand Down Expand Up @@ -120,4 +114,4 @@ private bool SafeIsReparsePoint(FileSystemInfo fsi)
return false;
}
}
}
}
44 changes: 44 additions & 0 deletions src/ByteSync.Client/Services/Inventories/NoiseFileDetector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System.Reflection;
using System.Text.Json;
using ByteSync.Common.Business.Misc;

namespace ByteSync.Services.Inventories;

public static class NoiseFileDetector
{
private const string NoiseFileResourceSuffix = ".Services.Inventories.noise-files.json";
private static readonly string[] KnownNoiseFileNames = LoadNoiseFileNames();

private static readonly HashSet<string> CaseSensitiveNoiseFileNames = new(KnownNoiseFileNames, StringComparer.Ordinal);
private static readonly HashSet<string> CaseInsensitiveNoiseFileNames = new(KnownNoiseFileNames, StringComparer.OrdinalIgnoreCase);

public static bool IsNoiseFileName(string? fileName, OSPlatforms os)
{
if (string.IsNullOrWhiteSpace(fileName))
{
return false;
}

return os == OSPlatforms.Linux
? CaseSensitiveNoiseFileNames.Contains(fileName)
: CaseInsensitiveNoiseFileNames.Contains(fileName);
}

private static string[] LoadNoiseFileNames()
{
var assembly = typeof(NoiseFileDetector).Assembly;
var resourceName = assembly.GetManifestResourceNames()
.Single(rn => rn.EndsWith(NoiseFileResourceSuffix, StringComparison.Ordinal));

using var stream = assembly.GetManifestResourceStream(resourceName);
ArgumentNullException.ThrowIfNull(stream);

var parsed = JsonSerializer.Deserialize<string[]>(stream);
ArgumentNullException.ThrowIfNull(parsed);

return parsed
.Where(s => !string.IsNullOrWhiteSpace(s))
.Distinct(StringComparer.Ordinal)
.ToArray();
}
}
17 changes: 17 additions & 0 deletions src/ByteSync.Client/Services/Inventories/noise-files.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[
"desktop.ini",
"thumbs.db",
"ehthumbs.db",
"ehthumbs_vista.db",
".desktop.ini",
".thumbs.db",
".DS_Store",
".AppleDouble",
".LSOverride",
".Spotlight-V100",
".Trashes",
".fseventsd",
".TemporaryItems",
".VolumeIcon.icns",
".directory"
]
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using ByteSync.Business.Inventories;
using ByteSync.Common.Business.Misc;
using ByteSync.Interfaces.Controls.Inventories;
using ByteSync.Services.Inventories;
using FluentAssertions;
Expand Down Expand Up @@ -131,4 +132,46 @@ public void ClassifyEntry_FallsBackToRegularFile_WhenPosixClassifierThrows()
Directory.Delete(tempDirectory.FullName, true);
}
}
}

[Test]
public void IsNoiseFileName_ShouldReturnTrue_ForKnownNoiseFile()
{
var inspector = new FileSystemInspector();
var tempDirectory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")));
var filePath = Path.Combine(tempDirectory.FullName, "thumbs.db");
File.WriteAllText(filePath, "x");
var fileInfo = new FileInfo(filePath);

try
{
var result = inspector.IsNoiseFileName(fileInfo, OSPlatforms.Windows);

result.Should().BeTrue();
}
finally
{
Directory.Delete(tempDirectory.FullName, true);
}
}

[Test]
public void IsNoiseFileName_ShouldReturnFalse_ForUnknownFile()
{
var inspector = new FileSystemInspector();
var tempDirectory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")));
var filePath = Path.Combine(tempDirectory.FullName, "regular.txt");
File.WriteAllText(filePath, "x");
var fileInfo = new FileInfo(filePath);

try
{
var result = inspector.IsNoiseFileName(fileInfo, OSPlatforms.Windows);

result.Should().BeFalse();
}
finally
{
Directory.Delete(tempDirectory.FullName, true);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using System.Text.Json;
using ByteSync.Common.Business.Misc;
using ByteSync.Services.Inventories;
using FluentAssertions;
using NUnit.Framework;

namespace ByteSync.Client.UnitTests.Services.Inventories;

public class NoiseFileDetectorTests
{
private static readonly string[] KnownNoiseFileNames = LoadNoiseFileNamesFromEmbeddedResource();

[TestCaseSource(nameof(KnownNoiseFileNames))]
public void IsNoiseFileName_ShouldReturnTrue_ForKnownNoiseFiles_OnWindows(string fileName)
{
var result = NoiseFileDetector.IsNoiseFileName(fileName, OSPlatforms.Windows);

result.Should().BeTrue();
}

[TestCaseSource(nameof(KnownNoiseFileNames))]
public void IsNoiseFileName_ShouldReturnTrue_ForKnownNoiseFiles_OnLinux(string fileName)
{
var result = NoiseFileDetector.IsNoiseFileName(fileName, OSPlatforms.Linux);

result.Should().BeTrue();
}

[TestCase("DESKTOP.INI")]
[TestCase("THUMBS.DB")]
[TestCase("EHTHUMBS.DB")]
[TestCase("EHTHUMBS_VISTA.DB")]
[TestCase(".ds_store")]
[TestCase(".appledouble")]
[TestCase(".lsoverride")]
[TestCase(".spotlight-v100")]
[TestCase(".trashes")]
[TestCase(".FSEVENTSD")]
[TestCase(".temporaryitems")]
[TestCase(".volumeicon.icns")]
[TestCase(".DIRECTORY")]
public void IsNoiseFileName_ShouldBeCaseInsensitive_OnNonLinuxPlatforms(string fileName)
{
var windowsResult = NoiseFileDetector.IsNoiseFileName(fileName, OSPlatforms.Windows);
var macResult = NoiseFileDetector.IsNoiseFileName(fileName, OSPlatforms.MacOs);

windowsResult.Should().BeTrue();
macResult.Should().BeTrue();
}

[TestCase("DESKTOP.INI")]
[TestCase("THUMBS.DB")]
[TestCase("EHTHUMBS.DB")]
[TestCase("EHTHUMBS_VISTA.DB")]
[TestCase(".ds_store")]
[TestCase(".appledouble")]
[TestCase(".lsoverride")]
[TestCase(".spotlight-v100")]
[TestCase(".trashes")]
[TestCase(".FSEVENTSD")]
[TestCase(".temporaryitems")]
[TestCase(".volumeicon.icns")]
[TestCase(".DIRECTORY")]
public void IsNoiseFileName_ShouldBeCaseSensitive_OnLinux(string fileName)
{
var result = NoiseFileDetector.IsNoiseFileName(fileName, OSPlatforms.Linux);

result.Should().BeFalse();
}

[TestCase("readme.md")]
[TestCase("normal.txt")]
[TestCase(".gitignore")]
public void IsNoiseFileName_ShouldReturnFalse_ForUnknownFileNames(string fileName)
{
var windowsResult = NoiseFileDetector.IsNoiseFileName(fileName, OSPlatforms.Windows);
var linuxResult = NoiseFileDetector.IsNoiseFileName(fileName, OSPlatforms.Linux);

windowsResult.Should().BeFalse();
linuxResult.Should().BeFalse();
}

[TestCase(null)]
[TestCase("")]
[TestCase(" ")]
public void IsNoiseFileName_ShouldReturnFalse_ForEmptyValues(string? fileName)
{
var windowsResult = NoiseFileDetector.IsNoiseFileName(fileName, OSPlatforms.Windows);
var linuxResult = NoiseFileDetector.IsNoiseFileName(fileName, OSPlatforms.Linux);

windowsResult.Should().BeFalse();
linuxResult.Should().BeFalse();
}

private static string[] LoadNoiseFileNamesFromEmbeddedResource()
{
var assembly = typeof(NoiseFileDetector).Assembly;
var resourceName = assembly.GetManifestResourceNames()
.SingleOrDefault(rn => rn.EndsWith(".Services.Inventories.noise-files.json", StringComparison.Ordinal));

resourceName.Should().NotBeNull();

using var stream = assembly.GetManifestResourceStream(resourceName!);
stream.Should().NotBeNull();

var data = JsonSerializer.Deserialize<string[]>(stream!);
data.Should().NotBeNull();

return data!;
}
}
Loading