diff --git a/Directory.Build.props b/Directory.Build.props
index 8c119d5..821e140 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,2 +1,16 @@
+
+
+
+
+
+
+
+
diff --git a/IconHelper.Test/ProcessDirectoryTests.cs b/IconHelper.Test/ProcessDirectoryTests.cs
index 757c8b2..f409a21 100644
--- a/IconHelper.Test/ProcessDirectoryTests.cs
+++ b/IconHelper.Test/ProcessDirectoryTests.cs
@@ -166,6 +166,27 @@ public void ContinuesAfterAFileThatCannotBeDecoded()
Assert.IsTrue(File.Exists(Path.Combine(output, "good.png")));
}
+ [TestMethod]
+ public void SkipsFileManagerMetadataWithoutCountingItAsFailed()
+ {
+ using TempDirectory temp = new();
+ string input = temp.Combine("in");
+ string output = temp.Combine("out");
+ Directory.CreateDirectory(input);
+ File.WriteAllBytes(Path.Join(input, ".DS_Store"), [0, 0, 0, 1, (byte)'B', (byte)'u', (byte)'d', (byte)'1']);
+ File.WriteAllText(Path.Join(input, "._good.png"), "AppleDouble resource fork");
+ File.WriteAllText(Path.Join(input, "desktop.ini"), "[.ShellClassInfo]\r\n");
+ File.WriteAllText(Path.Join(input, "THUMBS.DB"), "not an image");
+ File.WriteAllText(Path.Join(input, "ehthumbs.db"), "not an image");
+ WritePng(Path.Join(input, "good.png"), 64);
+
+ BatchResult result = IconHelper.ProcessDirectory(ArgumentsFor(input, output), NamedColors.White);
+
+ Assert.AreEqual(new BatchResult(1, 0), result, "File-manager metadata should be neither written nor failed.");
+ Assert.AreEqual(IconHelper.ExitSuccess, IconHelper.ExitCodeFor(result));
+ Assert.AreEqual(1, Directory.GetFiles(output).Length);
+ }
+
[TestMethod]
public void ReportsAndSkipsAFileThatCannotBeOpened()
{
diff --git a/IconHelper/IconHelper.cs b/IconHelper/IconHelper.cs
index de15552..2b1bd2c 100644
--- a/IconHelper/IconHelper.cs
+++ b/IconHelper/IconHelper.cs
@@ -125,7 +125,8 @@ internal static BatchResult ProcessDirectory(Arguments args, Color color)
// whole string let a directory anywhere above the input -- a user name, a date stamp,
// a project folder -- decide that every file below it was already generated. That
// failure is silent and total: nothing is written and the run still exits 0.
- if (Path.GetFileName(file).Contains(".new.png", StringComparison.Ordinal))
+ string fileName = Path.GetFileName(file);
+ if (fileName.Contains(".new.png", StringComparison.Ordinal) || IsFileManagerMetadata(fileName))
{
continue;
}
@@ -169,6 +170,18 @@ internal static BatchResult ProcessDirectory(Arguments args, Color color)
return new BatchResult(processed, failed);
}
+ ///
+ /// Names that Finder and Explorer drop into any folder they open: dot-files such as .DS_Store and
+ /// ._ AppleDouble files, plus Thumbs.db, ehthumbs.db and desktop.ini. They are not icons, so they
+ /// count as neither written nor failed; counting them as failures made every batch on such a
+ /// folder exit 2 and taught scripts to ignore the code that reports real decode failures.
+ ///
+ internal static bool IsFileManagerMetadata(string fileName)
+ => fileName.StartsWith('.')
+ || fileName.Equals("Thumbs.db", StringComparison.OrdinalIgnoreCase)
+ || fileName.Equals("ehthumbs.db", StringComparison.OrdinalIgnoreCase)
+ || fileName.Equals("desktop.ini", StringComparison.OrdinalIgnoreCase);
+
///
/// Reduces an icon to a coverage mask: every pixel carries the flat target colour and the shape
/// lives entirely in the alpha channel. The result is trimmed of its transparent margins, centred