diff --git a/CHANGELOG.md b/CHANGELOG.md index 917493915..89e128ec6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - [SIL.Media] BREAKING CHANGE (subtle and unlikely): WindowsAudioSession.OnPlaybackStopped now passes itself as the sender instead of a private implementation object, making the event arguments correct. ### Changed +- [SIL.Windows.Forms] PalasoImage robust load/save helpers now allow callers to override retry defaults, and the built-in retry lists were expanded for additional read/save exceptions seen in the wild. - [SIL.Core.Desktop, SIL.Windows.Forms, SIL.Windows.Forms.Keyboarding] Bumped L10NSharp to 10.0.0-beta0002 to support SIL.Core.Desktop with target `net8.0`; also updated the copyright to 2026 in each `AssemblyInfo.cs`. - [SIL.Windows.Forms.i18n, SIL.Core.Desktop.i18n] BREAKING CHANGE: Move L10NSharpLocalizer from Windows.Forms to Core.Desktop so it can be accessed without Winforms dependency. - [SIL.Windows.Forms.Clearshare] BREAKING CHANGE: Made LicenseInfo class independent of Windows Forms and moved it from SIL.Windows.Forms.Clearshare to SIL.Core.Clearshare. diff --git a/SIL.Windows.Forms/Clipboarding/WindowsClipboard.cs b/SIL.Windows.Forms/Clipboarding/WindowsClipboard.cs index 9a2994650..1471aa2f2 100644 --- a/SIL.Windows.Forms/Clipboarding/WindowsClipboard.cs +++ b/SIL.Windows.Forms/Clipboarding/WindowsClipboard.cs @@ -194,7 +194,7 @@ public PalasoImage GetImageFromClipboard() // This line gets all the file paths that were selected in explorer string[] files = dataObject.GetData(DataFormats.FileDrop) as string[]; - return files?.Where(RobustFile.Exists).Select(PalasoImage.FromFileRobustly).FirstOrDefault(); + return files?.Where(RobustFile.Exists).Select(path => PalasoImage.FromFileRobustly(path)).FirstOrDefault(); } if (Clipboard.ContainsText() && RobustFile.Exists(Clipboard.GetText())) diff --git a/SIL.Windows.Forms/ImageToolbox/PalasoImage.cs b/SIL.Windows.Forms/ImageToolbox/PalasoImage.cs index bfc990c3e..aad96345d 100644 --- a/SIL.Windows.Forms/ImageToolbox/PalasoImage.cs +++ b/SIL.Windows.Forms/ImageToolbox/PalasoImage.cs @@ -358,26 +358,39 @@ public static PalasoImage FromFile(string path) /// /// This would logically belong in SIL.Core.IO.RobustIO except that PalasoImage is in SIL.Windows.Forms. /// - public static PalasoImage FromFileRobustly(string path) + public static PalasoImage FromFileRobustly( + string path, + int maxRetryAttempts = RetryUtility.kDefaultMaxRetryAttempts, + int retryDelay = RetryUtility.kDefaultRetryDelay, + HashSet exceptionTypesToRetry = null + ) { + exceptionTypesToRetry ??= new HashSet + { + typeof(System.IO.IOException), + // Odd type to catch... but it seems that Image.FromFile (which is called in the bowels of PalasoImage.FromFile) + // throws OutOfMemoryException when the file is inaccessible. + // See http://stackoverflow.com/questions/2610416/is-there-a-reason-image-fromfile-throws-an-outofmemoryexception-for-an-invalid-i + typeof(System.OutOfMemoryException), + // Again you'd expect that if it's corrupt, it would stay that way, but + // experimentally, it seems we can get this if the file can't be read because it is (temporarily?) locked. + // (The text of the message reads, "File could not be read and is possible corrupted", which + // suggests they are using this to cover any case of not being able to read the file." + typeof(TagLib.CorruptFileException), + // Bloom saw this one in the wild. BL-16221 + typeof(System.Collections.Generic.KeyNotFoundException), + // Adding this simply because I'm adding it on the Save side. I'm tempted to just retry everything... + typeof(System.ApplicationException), + }; + try { - return RetryUtility.Retry(() => PalasoImage.FromFile(path), - RetryUtility.kDefaultMaxRetryAttempts, - RetryUtility.kDefaultRetryDelay, - new HashSet - { - typeof(System.IO.IOException), - // Odd type to catch... but it seems that Image.FromFile (which is called in the bowels of PalasoImage.FromFile) - // throws OutOfMemoryException when the file is inaccessible. - // See http://stackoverflow.com/questions/2610416/is-there-a-reason-image-fromfile-throws-an-outofmemoryexception-for-an-invalid-i - typeof(System.OutOfMemoryException), - // Again you'd expect that if it's corrupt, it would stay that way, but - // experimentally, it seems we can get this if the file can't be read because it is (temporarily?) locked. - // (The text of the message reads, "File could not be read and is possible corrupted", which - // suggests they are using this to cover any case of not being able to read the file." - typeof(TagLib.CorruptFileException) - }); + return RetryUtility.Retry( + () => PalasoImage.FromFile(path), + maxRetryAttempts, + retryDelay, + exceptionTypesToRetry + ); } catch (Exception e) { @@ -395,16 +408,27 @@ public static PalasoImage FromFileRobustly(string path) /// /// This would logically belong in SIL.Core.IO.RobustIO except that PalasoImage is in SIL.Windows.Forms. /// - public static void SaveImageRobustly(PalasoImage image, string fileName) + public static void SaveImageRobustly( + PalasoImage image, + string fileName, + int maxRetryAttempts = RetryUtility.kDefaultMaxRetryAttempts, + int retryDelay = RetryUtility.kDefaultRetryDelay, + HashSet retryOnExceptions = null) { + + retryOnExceptions ??= new HashSet + { + typeof(System.IO.IOException), + typeof(System.Runtime.InteropServices.ExternalException), + // PalasoImage.SaveImageSafely can also throw ApplicationExceptions + // (See https://github.com/sillsdev/libpalaso/blob/f2482a5b3c6c75b50ec5672b1eb731b1a040a05a/SIL.Windows.Forms/ImageToolbox/PalasoImage.cs#L155) + typeof(System.ApplicationException), + }; + RetryUtility.Retry(() => image.Save(fileName), - RetryUtility.kDefaultMaxRetryAttempts, - RetryUtility.kDefaultRetryDelay, - new HashSet - { - Type.GetType("System.IO.IOException"), - Type.GetType("System.Runtime.InteropServices.ExternalException") - }); + maxRetryAttempts, + retryDelay, + retryOnExceptions); } ///