Browse Source

Merge pull request #512 from PixiEditor/crash-fixes

Improve exception handling slightly
Krzysztof Krysiński 2 years ago
parent
commit
003ad4e2f8

+ 5 - 1
src/PixiEditor/Data/Localization/Languages/en.json

@@ -264,6 +264,7 @@
     "ERROR_SAVE_LOCATION": "Couldn't save the file to the specified location",
     "ERROR_WHILE_SAVING": "An internal error occured while saving. Please try again.",
     "UNKNOWN_ERROR_SAVING": "An error occured while saving.",
+    "ERROR_READING_FILE": "Error while reading the file",
     "FAILED_ASSOCIATE_LOSPEC": "Failed to associate Lospec Palette protocol.",
     "REDDIT": "Reddit",
     "GITHUB": "GitHub",
@@ -524,5 +525,8 @@
     "ALL_LAYERS": "All Layers",
     "SINGLE_LAYER": "Single Layer",
     "CHOOSE": "Choose",
-    "REMOVE": "Remove"
+    "REMOVE": "Remove",
+    "FILE_FORMAT_NOT_ASEPRITE_KEYS": "File is not a \".aseprite-keys\" file",
+    "FILE_HAS_INVALID_SHORTCUT": "The file contains an invalid shortcut",
+    "FILE_EXTENSION_NOT_SUPPORTED": "The file type '{0}' is not supported"
 }

+ 19 - 23
src/PixiEditor/Exceptions/CorruptedFileException.cs

@@ -1,27 +1,23 @@
-namespace PixiEditor.Exceptions;
+using System.IO;
+using System.Runtime.Serialization;
+using PixiEditor.Localization;
+
+namespace PixiEditor.Exceptions;
 
 [Serializable]
-internal class CorruptedFileException : Exception
+internal class CorruptedFileException : RecoverableException
 {
-    public CorruptedFileException()
-        : base("The file you've chosen might be corrupted.")
-    {
-    }
-
-    public CorruptedFileException(string message)
-        : base(message)
-    {
-    }
-
-    public CorruptedFileException(string message, Exception inner)
-        : base(message, inner)
-    {
-    }
-
-    protected CorruptedFileException(
-        System.Runtime.Serialization.SerializationInfo info,
-        System.Runtime.Serialization.StreamingContext context)
-        : base(info, context)
-    {
-    }
+    public CorruptedFileException() : base("FAILED_TO_OPEN_FILE") { }
+
+    public CorruptedFileException(Exception innerException) : base("FAILED_TO_OPEN_FILE", innerException) { }
+
+    public CorruptedFileException(LocalizedString displayMessage) : base(displayMessage) { }
+
+    public CorruptedFileException(LocalizedString displayMessage, Exception innerException) : base(displayMessage, innerException) { }
+
+    public CorruptedFileException(LocalizedString displayMessage, string exceptionMessage) : base(displayMessage, exceptionMessage) { }
+
+    public CorruptedFileException(LocalizedString displayMessage, string exceptionMessage, Exception innerException) : base(displayMessage, exceptionMessage, innerException) { }
+
+    protected CorruptedFileException(SerializationInfo info, StreamingContext context) : base(info, context) { }
 }

+ 20 - 0
src/PixiEditor/Exceptions/InvalidFileTypeException.cs

@@ -0,0 +1,20 @@
+using System.Runtime.Serialization;
+using PixiEditor.Localization;
+
+namespace PixiEditor.Exceptions;
+
+internal class InvalidFileTypeException : RecoverableException
+{
+    public InvalidFileTypeException() { }
+
+    public InvalidFileTypeException(LocalizedString displayMessage) : base(displayMessage) { }
+
+    public InvalidFileTypeException(LocalizedString displayMessage, Exception innerException) : base(displayMessage, innerException) { }
+
+    public InvalidFileTypeException(LocalizedString displayMessage, string exceptionMessage) : base(displayMessage, exceptionMessage) { }
+
+    public InvalidFileTypeException(LocalizedString displayMessage, string exceptionMessage, Exception innerException) : base(displayMessage, exceptionMessage, innerException) { }
+
+    protected InvalidFileTypeException(SerializationInfo info, StreamingContext context) : base(info, context) { }
+
+}

+ 21 - 0
src/PixiEditor/Exceptions/MissingFileException.cs

@@ -0,0 +1,21 @@
+using System.Runtime.Serialization;
+using PixiEditor.Localization;
+
+namespace PixiEditor.Exceptions;
+
+internal class MissingFileException : RecoverableException
+{
+    public MissingFileException() : base("FILE_NOT_FOUND") { }
+
+    public MissingFileException(Exception innerException) : base("FILE_NOT_FOUND", innerException) { }
+
+    public MissingFileException(LocalizedString displayMessage) : base(displayMessage) { }
+
+    public MissingFileException(LocalizedString displayMessage, Exception innerException) : base(displayMessage, innerException) { }
+
+    public MissingFileException(LocalizedString displayMessage, string exceptionMessage) : base(displayMessage, exceptionMessage) { }
+
+    public MissingFileException(LocalizedString displayMessage, string exceptionMessage, Exception innerException) : base(displayMessage, exceptionMessage, innerException) { }
+
+    protected MissingFileException(SerializationInfo info, StreamingContext context) : base(info, context) { }
+}

+ 36 - 0
src/PixiEditor/Exceptions/RecoverableException.cs

@@ -0,0 +1,36 @@
+using System.Runtime.Serialization;
+using PixiEditor.Localization;
+
+namespace PixiEditor.Exceptions;
+
+public class RecoverableException : Exception
+{
+    public LocalizedString DisplayMessage { get; set; }
+
+    public RecoverableException() 
+    {
+        DisplayMessage = "INTERNAL_ERROR";
+    }
+
+    public RecoverableException(LocalizedString displayMessage) 
+    {
+        DisplayMessage = displayMessage;
+    }
+
+    public RecoverableException(LocalizedString displayMessage, Exception innerException) : base(null, innerException) 
+    {
+        DisplayMessage = displayMessage;
+    }
+
+    public RecoverableException(LocalizedString displayMessage, string exceptionMessage) : base(exceptionMessage)
+    {
+        DisplayMessage = displayMessage;
+    }
+
+    public RecoverableException(LocalizedString displayMessage, string exceptionMessage, Exception innerException) : base(exceptionMessage, innerException)
+    {
+        DisplayMessage = displayMessage;
+    }
+
+    protected RecoverableException(SerializationInfo info, StreamingContext context) : base(info, context) { }
+}

+ 26 - 6
src/PixiEditor/Models/Commands/Templates/Providers/Parsers/AsepriteKeysParser.cs

@@ -1,6 +1,7 @@
 using System.IO;
 using System.Text;
 using System.Xml;
+using PixiEditor.Exceptions;
 
 namespace PixiEditor.Models.Commands.Templates.Parsers;
 
@@ -30,12 +31,12 @@ public class AsepriteKeysParser : KeysParser
     {
         if (!File.Exists(path))
         {
-            throw new FileNotFoundException("File not found", path);
+            throw new MissingFileException("FILE_NOT_FOUND", $"File {path} not found");
         }
 
         if (Path.GetExtension(path) != ".aseprite-keys")
         {
-            throw new ArgumentException("File is not aseprite-keys file", nameof(path));
+            throw new InvalidFileTypeException("FILE_FORMAT_NOT_ASEPRITE_KEYS", $"File {path} is not an aseprite-keys file");
         }
         
         return LoadAndParse(path, applyDefaults);
@@ -44,15 +45,34 @@ public class AsepriteKeysParser : KeysParser
     private ShortcutsTemplate LoadAndParse(string path, bool applyDefaults)
     {
         XmlDocument doc = new XmlDocument();
-        doc.Load(path);
+
+        try
+        {
+            doc.Load(path);
+        }
+        catch (Exception e) when (e is DirectoryNotFoundException or FileNotFoundException or PathTooLongException)
+        {
+            throw new MissingFileException("FILE_NOT_FOUND", e);
+        }
+        catch (Exception e)
+        {
+            throw new RecoverableException("FAILED_TO_OPEN_FILE", e);
+        }
         
         List<KeyDefinition> keyDefinitions = new List<KeyDefinition>(); // DefaultShortcut is actually mapped shortcut.
 
         LoadCommands(doc, keyDefinitions, applyDefaults);
         LoadTools(doc, keyDefinitions, applyDefaults);
-        
-        ShortcutsTemplate template = ShortcutsTemplate.FromKeyDefinitions(keyDefinitions);
-        return template;
+
+        try
+        {
+            return ShortcutsTemplate.FromKeyDefinitions(keyDefinitions);
+        }
+        catch (RecoverableException) { throw; }
+        catch (Exception e)
+        {
+            throw new CorruptedFileException("FILE_HAS_INVALID_SHORTCUT", e);
+        }
     }
 
     private void LoadCommands(XmlDocument document, List<KeyDefinition> keyDefinitions, bool applyDefaults)

+ 2 - 1
src/PixiEditor/Models/DataHolders/RecentlyOpenedDocument.cs

@@ -9,6 +9,7 @@ using PixiEditor.Helpers;
 using PixiEditor.Models.IO;
 using PixiEditor.Parser;
 using PixiEditor.Parser.Skia;
+using PixiEditor.Exceptions;
 
 namespace PixiEditor.Models.DataHolders;
 
@@ -126,7 +127,7 @@ internal class RecentlyOpenedDocument : NotifyableObject
             {
                 bitmap = Importer.ImportWriteableBitmap(FilePath);
             }
-            catch
+            catch (RecoverableException)
             {
                 corrupt = true;
                 return null;

+ 13 - 16
src/PixiEditor/Models/IO/Importer.cs

@@ -10,6 +10,7 @@ using PixiEditor.DrawingApi.Core.Surface.ImageData;
 using PixiEditor.DrawingApi.Core.Surface.PaintImpl;
 using PixiEditor.Exceptions;
 using PixiEditor.Helpers;
+using PixiEditor.Localization;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Parser;
 using PixiEditor.Parser.Deprecated;
@@ -51,13 +52,17 @@ internal class Importer : NotifyableObject
 
             return BitmapFactory.ConvertToPbgra32Format(bitmap);
         }
-        catch (NotSupportedException)
+        catch (NotSupportedException e)
         {
-            throw new CorruptedFileException($"The file type '{Path.GetExtension(path)}' is not supported");
+            throw new InvalidFileTypeException(new LocalizedString("FILE_EXTENSION_NOT_SUPPORTED", Path.GetExtension(path)), e);
         }
-        catch (FileFormatException)
+        catch (FileFormatException e)
         {
-            throw new CorruptedFileException("The file appears to be corrupted");
+            throw new CorruptedFileException("FAILED_TO_OPEN_FILE", e);
+        }
+        catch (Exception e)
+        {
+            throw new RecoverableException("ERROR_IMPORTING_IMAGE", e);
         }
     }
 
@@ -79,7 +84,7 @@ internal class Importer : NotifyableObject
             }
             catch (InvalidFileException e)
             {
-                throw new CorruptedFileException("The given file seems to be corrupted or from a newer version of PixiEditor", e);
+                throw new CorruptedFileException("FAILED_TO_OPEN_FILE", e);
             }
         }
     }
@@ -102,7 +107,7 @@ internal class Importer : NotifyableObject
             }
             catch (InvalidFileException e)
             {
-                throw new CorruptedFileException("The given file seems to be corrupted or from a newer version of PixiEditor", e);
+                throw new CorruptedFileException("FAILED_TO_OPEN_FILE", e);
             }
         }
     }
@@ -111,17 +116,9 @@ internal class Importer : NotifyableObject
     {
         if (!IsSupportedFile(path))
         {
-            throw new ArgumentException($"The file type '{Path.GetExtension(path)}' is not supported");
-        }
-        
-        try
-        {
-            return Path.GetExtension(path) != ".pixi" ? ImportWriteableBitmap(path) : PixiParser.Deserialize(path).ToDocument().PreviewBitmap;
-        }
-        catch (InvalidFileException)
-        {
-            throw new CorruptedFileException();
+            throw new InvalidFileTypeException(new LocalizedString("FILE_EXTENSION_NOT_SUPPORTED", Path.GetExtension(path)));
         }
+        return Path.GetExtension(path) != ".pixi" ? ImportWriteableBitmap(path) : PixiParser.Deserialize(path).ToDocument().PreviewBitmap;
     }
 
     public static bool IsSupportedFile(string path)

+ 18 - 5
src/PixiEditor/Models/IO/PaletteParsers/JascPalFile/JascFileException.cs

@@ -1,8 +1,21 @@
-namespace PixiEditor.Models.IO.PaletteParsers.JascPalFile;
+using System.Runtime.Serialization;
+using PixiEditor.Exceptions;
+using PixiEditor.Localization;
 
-internal class JascFileException : Exception
+namespace PixiEditor.Models.IO.PaletteParsers.JascPalFile;
+
+
+internal class JascFileException : RecoverableException
 {
-    public JascFileException(string message) : base(message)
-    {
-    }
+    public JascFileException() { }
+
+    public JascFileException(LocalizedString displayMessage) : base(displayMessage) { }
+
+    public JascFileException(LocalizedString displayMessage, Exception innerException) : base(displayMessage, innerException) { }
+
+    public JascFileException(LocalizedString displayMessage, string exceptionMessage) : base(displayMessage, exceptionMessage) { }
+
+    public JascFileException(LocalizedString displayMessage, string exceptionMessage, Exception innerException) : base(displayMessage, exceptionMessage, innerException) { }
+
+    protected JascFileException(SerializationInfo info, StreamingContext context) : base(info, context) { }
 }

+ 1 - 1
src/PixiEditor/Models/IO/PaletteParsers/JascPalFile/JascFileParser.cs

@@ -31,7 +31,7 @@ internal class JascFileParser : PaletteFileParser
             return new PaletteFileData(name, colors);
         }
 
-        throw new JascFileException("Invalid JASC-PAL file.");
+        throw new JascFileException("FAILED_TO_OPEN_FILE", "Invalid JASC-PAL file.");
     }
 
     public static async Task<bool> SaveFile(string path, PaletteFileData data)

+ 6 - 5
src/PixiEditor/ViewModels/ImportFilePopupViewModel.cs

@@ -3,6 +3,7 @@ using PixiEditor.Helpers;
 using PixiEditor.Models.IO;
 using System;
 using System.IO;
+using System.Runtime.InteropServices;
 using System.Windows;
 using System.Windows.Media.Imaging;
 
@@ -79,13 +80,13 @@ internal class ImportFilePopupViewModel : ViewModelBase
                 ImportHeight = bitmap.PixelHeight;
                 ImportWidth = bitmap.PixelWidth;
             }
-            catch (NotSupportedException)
+            catch (Exception e) when (e is NotSupportedException or FileFormatException)
             {
-                throw new CorruptedFileException();
+                throw new CorruptedFileException("FAILED_TO_OPEN_FILE", e);
             }
-            catch (FileFormatException)
+            catch (COMException e)
             {
-                throw new CorruptedFileException();
+                throw new RecoverableException("INTERNAL_ERROR", e);
             }
         }
     }
@@ -106,4 +107,4 @@ internal class ImportFilePopupViewModel : ViewModelBase
         ((Window)parameter).DialogResult = true;
         CloseButton(parameter);
     }
-}
+}

+ 21 - 13
src/PixiEditor/ViewModels/SettingsWindowViewModel.cs

@@ -12,6 +12,7 @@ using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.Models.Commands.Templates;
 using PixiEditor.Models.UserPreferences;
 using PixiEditor.Views.Dialogs;
+using PixiEditor.Exceptions;
 
 namespace PixiEditor.ViewModels;
 
@@ -122,18 +123,8 @@ internal class SettingsWindowViewModel : ViewModelBase
         if (dialog.ShowDialog().GetValueOrDefault())
         {
             List<Shortcut> shortcuts = new List<Shortcut>();
-            try
-            {
-                if (!TryImport(dialog, ref shortcuts))
-                {
-                    return;
-                }
-            }
-            catch (Exception e)
-            {
-                NoticeDialog.Show("SHORTCUTS_FILE_INCORRECT_FORMAT", "INVALID_FILE");
+            if (!TryImport(dialog, ref shortcuts))
                 return;
-            }
             
             CommandController.Current.ResetShortcuts();
             CommandController.Current.Import(shortcuts, false);
@@ -148,7 +139,16 @@ internal class SettingsWindowViewModel : ViewModelBase
     {
         if (dialog.FileName.EndsWith(".pixisc") || dialog.FileName.EndsWith(".json"))
         {
-            shortcuts = ShortcutFile.LoadTemplate(dialog.FileName)?.Shortcuts.ToList();
+            try
+            {
+                shortcuts = ShortcutFile.LoadTemplate(dialog.FileName)?.Shortcuts.ToList();
+            }
+            catch (Exception)
+            {
+                NoticeDialog.Show(title: "ERROR", message: "ERROR_READING_FILE");
+                return false;
+            }
+
             if (shortcuts is null)
             {
                 NoticeDialog.Show("SHORTCUTS_FILE_INCORRECT_FORMAT", "INVALID_FILE");
@@ -165,7 +165,15 @@ internal class SettingsWindowViewModel : ViewModelBase
                 return false;
             }
 
-            shortcuts = provider.KeysParser.Parse(dialog.FileName, false)?.Shortcuts.ToList();
+            try
+            {
+                shortcuts = provider.KeysParser.Parse(dialog.FileName, false)?.Shortcuts.ToList();
+            }
+            catch (RecoverableException e)
+            {
+                NoticeDialog.Show(title: "ERROR", message: e.DisplayMessage);
+                return false;
+            }
         }
 
         return true;

+ 2 - 2
src/PixiEditor/ViewModels/SubViewModels/Main/FileViewModel.cs

@@ -172,9 +172,9 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
                 OpenRegularImage(path);
             }
         }
-        catch (CorruptedFileException ex)
+        catch (RecoverableException ex)
         {
-            NoticeDialog.Show(ex.Message, "FAILED_TO_OPEN_FILE");
+            NoticeDialog.Show(ex.DisplayMessage, "ERROR");
         }
         catch (OldFileFormatException)
         {

+ 3 - 2
src/PixiEditor/ViewModels/SubViewModels/Main/LayersViewModel.cs

@@ -6,6 +6,7 @@ using System.Windows.Media.Imaging;
 using Microsoft.Win32;
 using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.Exceptions;
 using PixiEditor.Localization;
 using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.Models.Controllers;
@@ -358,9 +359,9 @@ internal class LayersViewModel : SubViewModel<ViewModelMain>
         {
             bitmap = Importer.ImportWriteableBitmap(path);
         }
-        catch (Exception e)
+        catch (RecoverableException e)
         {
-            NoticeDialog.Show("ERROR_IMPORTING_IMAGE", "ERROR");
+            NoticeDialog.Show(title: "ERROR", message: e.DisplayMessage);
             return;
         }
 

+ 3 - 2
src/PixiEditor/Views/Dialogs/ImportShortcutTemplatePopup.xaml.cs

@@ -1,5 +1,6 @@
 using System.Windows;
 using System.Windows.Input;
+using PixiEditor.Exceptions;
 using PixiEditor.Localization;
 using PixiEditor.Models.Commands;
 using PixiEditor.Models.Commands.Attributes.Commands;
@@ -51,9 +52,9 @@ internal partial class ImportShortcutTemplatePopup : Window
         {
             CommandController.Current.Import(defaults.GetInstalledShortcuts().Shortcuts);
         }
-        catch
+        catch (RecoverableException e)
         {
-            NoticeDialog.Show($"FILE_INCORRECT_FORMAT", "ERROR");
+            NoticeDialog.Show(e.DisplayMessage, "ERROR");
             return;
         }