From 712cd36486910547efaff2618921311d816c8277 Mon Sep 17 00:00:00 2001 From: tmedwards Date: Fri, 31 Jul 2015 05:00:15 -0500 Subject: [PATCH] Save enumeration crash fix and updates. * Fixes startup crashes during save enumeration from exceptions thrown while reading save files (mostly from corrupt saves). * Unifies save enumerations regardless of which Flash directory is being populated, so they all behave the same. * IO errors now get a message similar to permission errors. --- CoCEd/App.xaml.cs | 27 ++++++++++----- CoCEd/Model/AmfFile.cs | 69 ++++++++++++++++++++++---------------- CoCEd/Model/FileManager.cs | 69 +++++++++++++++++++++++++++----------- CoCEd/ViewModel/FilesVM.cs | 2 +- CoCEd/ViewModel/VM.cs | 8 ++--- 5 files changed, 113 insertions(+), 62 deletions(-) diff --git a/CoCEd/App.xaml.cs b/CoCEd/App.xaml.cs index b95debe..e71852f 100644 --- a/CoCEd/App.xaml.cs +++ b/CoCEd/App.xaml.cs @@ -113,16 +113,27 @@ void Initialize() VM.Create(); FileManager.BuildPaths(); - var directories = FileManager.GetDirectories().ToArray(); // Load all on startup to check for errors + var directories = FileManager.GetDirectories().ToArray(); // Load all on startup to check for errors var result = ExceptionBoxResult.Continue; - if (FileManager.PathWithMissingPermissions != null) + switch (FileManager.Result) { - box = new ExceptionBox(); - box.Title = "Could not scan some folders."; - box.Message = "CoCEd did not get permission to read a folder or a file.\nSome files won't be displayed in the open/save menus."; - box.Path = FileManager.PathWithMissingPermissions; - box.IsWarning = true; - result = box.ShowDialog(ExceptionBoxButtons.Quit, ExceptionBoxButtons.Continue); + case FileEnumerationResult.NoPermission: + box = new ExceptionBox(); + box.Title = "Could not scan some folders."; + box.Message = "CoCEd did not get permission to read a folder or file.\nSome files will not be displayed in the Open/Save menus."; + box.Path = FileManager.ResultPath; + box.IsWarning = true; + result = box.ShowDialog(ExceptionBoxButtons.Quit, ExceptionBoxButtons.Continue); + break; + + case FileEnumerationResult.Unreadable: + box = new ExceptionBox(); + box.Title = "Could not read some folders."; + box.Message = "CoCEd could not read a folder or file.\nSome files will not be displayed in the Open/Save menus."; + box.Path = FileManager.ResultPath; + box.IsWarning = true; + result = box.ShowDialog(ExceptionBoxButtons.Quit, ExceptionBoxButtons.Continue); + break; } if (result == ExceptionBoxResult.Quit) { diff --git a/CoCEd/Model/AmfFile.cs b/CoCEd/Model/AmfFile.cs index b593191..5e3f23b 100644 --- a/CoCEd/Model/AmfFile.cs +++ b/CoCEd/Model/AmfFile.cs @@ -11,20 +11,19 @@ namespace CoCEd.Model { public enum SerializationFormat { - Slot = 0, - Exported = 1, + Slot, + Exported, } - public sealed class AmfFile : AmfObject { static readonly HashSet _backedUpFiles = new HashSet(); - public AmfFile(string path) : base(AmfTypes.Array) { FilePath = path; + Name = Path.GetFileNameWithoutExtension(FilePath); Date = File.GetLastWriteTime(path); try { @@ -40,31 +39,15 @@ public AmfFile(string path) } } } -#if !DEBUG - catch (IOException e) - { - Error = e.ToString(); - } - catch (InvalidOperationException e) - { - Error = e.ToString(); - } - catch (ArgumentException e) - { - Error = e.ToString(); - } - catch (NotImplementedException e) - { - Error = e.ToString(); - } - catch (UnauthorizedAccessException e) + // All exceptions need to be handled as the general case, since corrupt + // saves can also cause exceptions (e.g. InvalidCastException), however, + // we will flag permission and IO issues for consumers like FileManager + catch (Exception e) { - Error = e.ToString(); - } -#endif - catch (SecurityException e) - { - Error = e.ToString(); + AmfFileError.Error type = AmfFileError.Error.Unknown; + if (e is SecurityException || e is UnauthorizedAccessException) type = AmfFileError.Error.NoPermission; + else if (e is IOException) type = AmfFileError.Error.Unreadable; + Error = new AmfFileError(type, e.ToString()); } } @@ -86,7 +69,7 @@ public string Name private set; } - public string Error + public AmfFileError Error { get; private set; @@ -185,4 +168,32 @@ public void TestSerialization() } #endif } + + public sealed class AmfFileError + { + public enum Error + { + Unknown, + NoPermission, + Unreadable, + } + + public Error Type { get; private set; } + public string Mesg { get; private set; } + + public AmfFileError(Error type, string mesg) + { + Type = type; + Mesg = mesg; + } + public AmfFileError(string mesg) + : this(Error.Unknown, mesg) + { + } + + public override string ToString() + { + return Mesg; + } + } } diff --git a/CoCEd/Model/FileManager.cs b/CoCEd/Model/FileManager.cs index 0a8708a..be46774 100644 --- a/CoCEd/Model/FileManager.cs +++ b/CoCEd/Model/FileManager.cs @@ -15,6 +15,13 @@ public enum DirectoryKind Backup, } + public enum FileEnumerationResult + { + Success, + NoPermission, + Unreadable, + } + public class FlashDirectory { public string Name; @@ -49,7 +56,8 @@ public static int SaveSlotsUpperBoundByGame get { return VM.Instance.Game.IsRevampMod ? MaxSaveSlotsRevampMod : MaxSaveSlotsCoC; } } - public static string PathWithMissingPermissions { get; private set; } + public static FileEnumerationResult Result { get; private set; } + public static string ResultPath { get; private set; } public static string BackupPath { @@ -62,6 +70,8 @@ public static string BackupPath public static void BuildPaths() { + Result = FileEnumerationResult.Success; + const string standardPath = @"Macromedia\Flash Player\#SharedObjects\"; const string chromePath1 = @"Google\Chrome\User Data\Default\Pepper Data\Shockwave Flash\WritableRoot\#SharedObjects\"; const string chromePath2 = @"Google\Chrome\User Data\Profile 1\Pepper Data\Shockwave Flash\WritableRoot\#SharedObjects\"; // Win 8/8.1 thing, apparently @@ -127,14 +137,18 @@ static void BuildPath(string nameFormat, Environment.SpecialFolder root, string[ } catch (SecurityException) { - PathWithMissingPermissions = path; + Result = FileEnumerationResult.NoPermission; + ResultPath = path; } catch (UnauthorizedAccessException) { - PathWithMissingPermissions = path; + Result = FileEnumerationResult.NoPermission; + ResultPath = path; } catch (IOException) { + Result = FileEnumerationResult.Unreadable; + ResultPath = path; } } @@ -152,14 +166,20 @@ public static FlashDirectory CreateBackupDirectory() var dir = new FlashDirectory("Backup", BackupPath, true, DirectoryKind.Backup); var dirInfo = new DirectoryInfo(BackupPath); - foreach (var file in dirInfo.GetFiles("*.bak").OrderByDescending(x => x.LastWriteTimeUtc)) dir.Files.Add(new AmfFile(file.FullName)); + foreach (var filePath in dirInfo.GetFiles("*.bak").OrderByDescending(x => x.LastWriteTimeUtc).Select(x => x.FullName)) + { + AddFileToDirectory(dir, filePath); + } return dir; } static FlashDirectory CreateExternalDirectory() { var dir = new FlashDirectory("External", "", true, DirectoryKind.Backup); - foreach (var path in _externalPaths) dir.Files.Add(new AmfFile(path)); + foreach (var filePath in _externalPaths) + { + AddFileToDirectory(dir, filePath); + } return dir; } @@ -171,24 +191,33 @@ static FlashDirectory CreateDirectory(FlashDirectory dir) for (int i = SaveSlotsLowerBound; i <= SaveSlotsUpperBound; i++) { var filePath = Path.Combine(dir.Path, "CoC_" + i + ".sol"); - try - { - if (!File.Exists(filePath)) continue; - dir.Files.Add(new AmfFile(filePath)); - } - catch (SecurityException) - { - PathWithMissingPermissions = filePath; - } - catch (UnauthorizedAccessException) - { - PathWithMissingPermissions = filePath; - } - catch (IOException) + AddFileToDirectory(dir, filePath); + } + return dir; + } + + private static bool AddFileToDirectory(FlashDirectory dir, string filePath) + { + if (!File.Exists(filePath)) return false; + + var amfFile = new AmfFile(filePath); + if (amfFile.Error != null) + { + switch (amfFile.Error.Type) { + case AmfFileError.Error.NoPermission: + Result = FileEnumerationResult.NoPermission; + ResultPath = filePath; + return false; + + case AmfFileError.Error.Unreadable: + Result = FileEnumerationResult.Unreadable; + ResultPath = filePath; + return false; } } - return dir; + dir.Files.Add(amfFile); + return true; } public static void TryRegisterExternalFile(string path) diff --git a/CoCEd/ViewModel/FilesVM.cs b/CoCEd/ViewModel/FilesVM.cs index 0f5406f..abbc416 100644 --- a/CoCEd/ViewModel/FilesVM.cs +++ b/CoCEd/ViewModel/FilesVM.cs @@ -407,7 +407,7 @@ public Image Icon { get { - if (String.IsNullOrEmpty(Source.Error)) return null; + if (Source.Error == null) return null; BitmapImage bmp = new BitmapImage(); bmp.BeginInit(); diff --git a/CoCEd/ViewModel/VM.cs b/CoCEd/ViewModel/VM.cs index 4fd670d..2a4c4fd 100644 --- a/CoCEd/ViewModel/VM.cs +++ b/CoCEd/ViewModel/VM.cs @@ -62,17 +62,17 @@ public void Load(string path, SerializationFormat expectedFormat, bool createBac var file = new AmfFile(path); var dataVersion = file.GetString("version"); - if (!String.IsNullOrEmpty(file.Error)) + if (file.Error != null) { var box = new ExceptionBox(); - box.Title = "Could not scan some folders."; + box.Title = "Could not read file."; box.Message = "CoCEd could not read this file correctly. Maybe it was corrupted or generated by an old version of Flash. Continuing may make CoCEd unstable or cause it to corrupt the file. It is strongly advised that you cancel this operation."; box.Path = file.FilePath; - box.ExceptionMessage = file.Error; + box.ExceptionMessage = file.Error.Mesg; box.IsWarning = true; var result = box.ShowDialog(ExceptionBoxButtons.Continue, ExceptionBoxButtons.Cancel); - Logger.Error(file.Error); + Logger.Error(file.Error.Mesg); if (result != ExceptionBoxResult.Continue) return; } else if (String.IsNullOrEmpty(dataVersion))