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