Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion SIL.Windows.Forms/Clipboarding/WindowsClipboard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()))
Expand Down
74 changes: 49 additions & 25 deletions SIL.Windows.Forms/ImageToolbox/PalasoImage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -358,26 +358,39 @@ public static PalasoImage FromFile(string path)
/// <remarks>
/// This would logically belong in SIL.Core.IO.RobustIO except that PalasoImage is in SIL.Windows.Forms.
/// </remarks>
public static PalasoImage FromFileRobustly(string path)
public static PalasoImage FromFileRobustly(
string path,
int maxRetryAttempts = RetryUtility.kDefaultMaxRetryAttempts,
int retryDelay = RetryUtility.kDefaultRetryDelay,
HashSet<Type> exceptionTypesToRetry = null
)
{
exceptionTypesToRetry ??= new HashSet<Type>
{
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<Type>
{
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)
{
Expand All @@ -395,16 +408,27 @@ public static PalasoImage FromFileRobustly(string path)
/// <remarks>
/// This would logically belong in SIL.Core.IO.RobustIO except that PalasoImage is in SIL.Windows.Forms.
/// </remarks>
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<Type> retryOnExceptions = null)
{

retryOnExceptions ??= new HashSet<Type>
{
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>
{
Type.GetType("System.IO.IOException"),
Type.GetType("System.Runtime.InteropServices.ExternalException")
});
maxRetryAttempts,
retryDelay,
retryOnExceptions);
}

/// <summary>
Expand Down
Loading