From 5581e25a2150b7a5cc707b7a38371dd48a29c4de Mon Sep 17 00:00:00 2001 From: stefansjfw <57057282+stefansjfw@users.noreply.github.com> Date: Mon, 9 Mar 2020 10:41:06 +0100 Subject: [PATCH] Editor IO exception handling (#1491) * Close input stream Show MessageBox with exception message Remove unused arguments Guard all of the Editor input/output code parts and show MessageBox with appropriate message and issue reporting link * Extract showing messageBox into method --- .../editor/FancyZonesEditor/EditorWindow.cs | 2 +- .../FancyZonesEditor/MainWindow.xaml.cs | 4 +- .../Models/CanvasLayoutModel.cs | 73 ++--- .../Models/GridLayoutModel.cs | 90 ++++--- .../FancyZonesEditor/Models/LayoutModel.cs | 253 ++++++++++-------- .../FancyZonesEditor/Models/Settings.cs | 84 +++--- 6 files changed, 274 insertions(+), 232 deletions(-) diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/EditorWindow.cs b/src/modules/fancyzones/editor/FancyZonesEditor/EditorWindow.cs index ed11e357dd..f36037cb48 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/EditorWindow.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/EditorWindow.cs @@ -16,7 +16,7 @@ namespace FancyZonesEditor EditorOverlay mainEditor = EditorOverlay.Current; if (mainEditor.DataContext is LayoutModel model) { - model.Persist(mainEditor.GetZoneRects()); + model.Persist(); } _choosing = true; diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml.cs index 8481a5b113..1a4106cb58 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml.cs @@ -150,11 +150,11 @@ namespace FancyZonesEditor { if (model is GridLayoutModel) { - model.Apply(mainEditor.GetZoneRects()); + model.Apply(); } else { - model.Apply((model as CanvasLayoutModel).Zones.ToArray()); + model.Apply(); } Close(); diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/CanvasLayoutModel.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/CanvasLayoutModel.cs index 094aa3a03a..2d928b5a24 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Models/CanvasLayoutModel.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/CanvasLayoutModel.cs @@ -117,46 +117,53 @@ namespace FancyZonesEditor.Models // Implements the LayoutModel.PersistData abstract method protected override void PersistData() { - FileStream outputStream = File.Open(Settings.AppliedZoneSetTmpFile, FileMode.Create); - JsonWriterOptions writerOptions = new JsonWriterOptions + try { - SkipValidation = true, - }; - using (var writer = new Utf8JsonWriter(outputStream, writerOptions)) - { - writer.WriteStartObject(); - writer.WriteString("uuid", "{" + Guid.ToString().ToUpper() + "}"); - writer.WriteString("name", Name); - - writer.WriteString("type", "canvas"); - - writer.WriteStartObject("info"); - - writer.WriteNumber("ref-width", _referenceWidth); - writer.WriteNumber("ref-height", _referenceHeight); - - writer.WriteStartArray("zones"); - foreach (Int32Rect rect in Zones) + FileStream outputStream = File.Open(Settings.AppliedZoneSetTmpFile, FileMode.Create); + JsonWriterOptions writerOptions = new JsonWriterOptions + { + SkipValidation = true, + }; + using (var writer = new Utf8JsonWriter(outputStream, writerOptions)) { writer.WriteStartObject(); - writer.WriteNumber("X", rect.X); - writer.WriteNumber("Y", rect.Y); - writer.WriteNumber("width", rect.Width); - writer.WriteNumber("height", rect.Height); + writer.WriteString("uuid", "{" + Guid.ToString().ToUpper() + "}"); + writer.WriteString("name", Name); + + writer.WriteString("type", "canvas"); + + writer.WriteStartObject("info"); + + writer.WriteNumber("ref-width", _referenceWidth); + writer.WriteNumber("ref-height", _referenceHeight); + + writer.WriteStartArray("zones"); + foreach (Int32Rect rect in Zones) + { + writer.WriteStartObject(); + writer.WriteNumber("X", rect.X); + writer.WriteNumber("Y", rect.Y); + writer.WriteNumber("width", rect.Width); + writer.WriteNumber("height", rect.Height); + writer.WriteEndObject(); + } + + writer.WriteEndArray(); + + // end info object writer.WriteEndObject(); + + // end root object + writer.WriteEndObject(); + writer.Flush(); } - writer.WriteEndArray(); - - // end info object - writer.WriteEndObject(); - - // end root object - writer.WriteEndObject(); - writer.Flush(); + outputStream.Close(); + } + catch (Exception ex) + { + ShowExceptionMessageBox("Error persisting canvas layout", ex); } - - outputStream.Close(); } } } diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/GridLayoutModel.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/GridLayoutModel.cs index 9641dbb63f..74501cac0a 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Models/GridLayoutModel.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/GridLayoutModel.cs @@ -2,10 +2,11 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.IO; -using System.Text; using System.Text.Json; +using System.Windows; namespace FancyZonesEditor.Models { @@ -170,59 +171,66 @@ namespace FancyZonesEditor.Models // Implements the LayoutModel.PersistData abstract method protected override void PersistData() { - FileStream outputStream = File.Open(Settings.AppliedZoneSetTmpFile, FileMode.Create); - using (var writer = new Utf8JsonWriter(outputStream, options: default)) + try { - writer.WriteStartObject(); - writer.WriteString("uuid", "{" + Guid.ToString().ToUpper() + "}"); - writer.WriteString("name", Name); - - writer.WriteString("type", "grid"); - - writer.WriteStartObject("info"); - - writer.WriteNumber("rows", Rows); - writer.WriteNumber("columns", Columns); - - writer.WriteStartArray("rows-percentage"); - for (int row = 0; row < Rows; row++) + FileStream outputStream = File.Open(Settings.AppliedZoneSetTmpFile, FileMode.Create); + using (var writer = new Utf8JsonWriter(outputStream, options: default)) { - writer.WriteNumberValue(RowPercents[row]); - } + writer.WriteStartObject(); + writer.WriteString("uuid", "{" + Guid.ToString().ToUpper() + "}"); + writer.WriteString("name", Name); - writer.WriteEndArray(); + writer.WriteString("type", "grid"); - writer.WriteStartArray("columns-percentage"); - for (int col = 0; col < Columns; col++) - { - writer.WriteNumberValue(ColumnPercents[col]); - } + writer.WriteStartObject("info"); - writer.WriteEndArray(); + writer.WriteNumber("rows", Rows); + writer.WriteNumber("columns", Columns); - writer.WriteStartArray("cell-child-map"); - for (int row = 0; row < Rows; row++) - { - writer.WriteStartArray(); - for (int col = 0; col < Columns; col++) + writer.WriteStartArray("rows-percentage"); + for (int row = 0; row < Rows; row++) { - writer.WriteNumberValue(CellChildMap[row, col]); + writer.WriteNumberValue(RowPercents[row]); } writer.WriteEndArray(); + + writer.WriteStartArray("columns-percentage"); + for (int col = 0; col < Columns; col++) + { + writer.WriteNumberValue(ColumnPercents[col]); + } + + writer.WriteEndArray(); + + writer.WriteStartArray("cell-child-map"); + for (int row = 0; row < Rows; row++) + { + writer.WriteStartArray(); + for (int col = 0; col < Columns; col++) + { + writer.WriteNumberValue(CellChildMap[row, col]); + } + + writer.WriteEndArray(); + } + + writer.WriteEndArray(); + + // end info object + writer.WriteEndObject(); + + // end root object + writer.WriteEndObject(); + writer.Flush(); } - writer.WriteEndArray(); - - // end info object - writer.WriteEndObject(); - - // end root object - writer.WriteEndObject(); - writer.Flush(); + outputStream.Close(); + } + catch (Exception ex) + { + ShowExceptionMessageBox("Error persisting grid layout", ex); } - - outputStream.Close(); } } } diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs index 72c23a780f..846c140f8e 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs @@ -27,8 +27,12 @@ namespace FancyZonesEditor.Models // Manages common properties and base persistence public abstract class LayoutModel : INotifyPropertyChanged { - private static readonly string _registryPath = Settings.RegistryPath + "\\Layouts"; - private static readonly string _fullRegistryPath = Settings.FullRegistryPath + "\\Layouts"; + public static void ShowExceptionMessageBox(string message, Exception ex) + { + string title = "FancyZones Editor Exception Handler"; + string fullMessage = "Please report the bug to https://github.com/microsoft/PowerToys/issues \n" + message + ": " + ex.Message; + MessageBox.Show(fullMessage, title); + } protected LayoutModel() { @@ -133,19 +137,26 @@ namespace FancyZonesEditor.Models public static void SerializeDeletedCustomZoneSets() { - FileStream outputStream = File.Open(Settings.CustomZoneSetsTmpFile, FileMode.Create); - var writer = new Utf8JsonWriter(outputStream, options: default); - writer.WriteStartObject(); - writer.WriteStartArray("deleted-custom-zone-sets"); - foreach (string zoneSet in _deletedCustomModels) + try { - writer.WriteStringValue(zoneSet); - } + FileStream outputStream = File.Open(Settings.CustomZoneSetsTmpFile, FileMode.Create); + var writer = new Utf8JsonWriter(outputStream, options: default); + writer.WriteStartObject(); + writer.WriteStartArray("deleted-custom-zone-sets"); + foreach (string zoneSet in _deletedCustomModels) + { + writer.WriteStringValue(zoneSet); + } - writer.WriteEndArray(); - writer.WriteEndObject(); - writer.Flush(); - outputStream.Close(); + writer.WriteEndArray(); + writer.WriteEndObject(); + writer.Flush(); + outputStream.Close(); + } + catch (Exception ex) + { + ShowExceptionMessageBox("Error serializing deleted layouts", ex); + } } // Loads all the custom Layouts from tmp file passed by FancuZonesLib @@ -153,79 +164,81 @@ namespace FancyZonesEditor.Models { _customModels = new ObservableCollection(); - FileStream inputStream = File.Open(Settings.CustomZoneSetsTmpFile, FileMode.Open); - JsonDocument jsonObject; try { - jsonObject = JsonDocument.Parse(inputStream, options: default); - } - catch - { - return _customModels; - } + FileStream inputStream = File.Open(Settings.CustomZoneSetsTmpFile, FileMode.Open); + JsonDocument jsonObject = JsonDocument.Parse(inputStream, options: default); + JsonElement.ArrayEnumerator customZoneSetsEnumerator = jsonObject.RootElement.GetProperty("custom-zone-sets").EnumerateArray(); - JsonElement.ArrayEnumerator customZoneSetsEnumerator = jsonObject.RootElement.GetProperty("custom-zone-sets").EnumerateArray(); - while (customZoneSetsEnumerator.MoveNext()) - { - var current = customZoneSetsEnumerator.Current; - string name = current.GetProperty("name").GetString(); - string type = current.GetProperty("type").GetString(); - string uuid = current.GetProperty("uuid").GetString(); - var info = current.GetProperty("info"); - if (type.Equals("grid")) + while (customZoneSetsEnumerator.MoveNext()) { - int rows = info.GetProperty("rows").GetInt32(); - int columns = info.GetProperty("columns").GetInt32(); - int[] rowsPercentage = new int[rows]; - JsonElement.ArrayEnumerator rowsPercentageEnumerator = info.GetProperty("rows-percentage").EnumerateArray(); - int i = 0; - while (rowsPercentageEnumerator.MoveNext()) + var current = customZoneSetsEnumerator.Current; + string name = current.GetProperty("name").GetString(); + string type = current.GetProperty("type").GetString(); + string uuid = current.GetProperty("uuid").GetString(); + var info = current.GetProperty("info"); + if (type.Equals("grid")) { - rowsPercentage[i++] = rowsPercentageEnumerator.Current.GetInt32(); - } - - i = 0; - int[] columnsPercentage = new int[columns]; - JsonElement.ArrayEnumerator columnsPercentageEnumerator = info.GetProperty("columns-percentage").EnumerateArray(); - while (columnsPercentageEnumerator.MoveNext()) - { - columnsPercentage[i++] = columnsPercentageEnumerator.Current.GetInt32(); - } - - i = 0; - JsonElement.ArrayEnumerator cellChildMapRows = info.GetProperty("cell-child-map").EnumerateArray(); - int[,] cellChildMap = new int[rows, columns]; - while (cellChildMapRows.MoveNext()) - { - int j = 0; - JsonElement.ArrayEnumerator cellChildMapRowElems = cellChildMapRows.Current.EnumerateArray(); - while (cellChildMapRowElems.MoveNext()) + int rows = info.GetProperty("rows").GetInt32(); + int columns = info.GetProperty("columns").GetInt32(); + int[] rowsPercentage = new int[rows]; + JsonElement.ArrayEnumerator rowsPercentageEnumerator = info.GetProperty("rows-percentage").EnumerateArray(); + int i = 0; + while (rowsPercentageEnumerator.MoveNext()) { - cellChildMap[i, j++] = cellChildMapRowElems.Current.GetInt32(); + rowsPercentage[i++] = rowsPercentageEnumerator.Current.GetInt32(); } - i++; - } + i = 0; + int[] columnsPercentage = new int[columns]; + JsonElement.ArrayEnumerator columnsPercentageEnumerator = info.GetProperty("columns-percentage").EnumerateArray(); + while (columnsPercentageEnumerator.MoveNext()) + { + columnsPercentage[i++] = columnsPercentageEnumerator.Current.GetInt32(); + } - _customModels.Add(new GridLayoutModel(uuid, name, LayoutType.Custom, rows, columns, rowsPercentage, columnsPercentage, cellChildMap)); - } - else if (type.Equals("canvas")) - { - int referenceWidth = info.GetProperty("ref-width").GetInt32(); - int referenceHeight = info.GetProperty("ref-height").GetInt32(); - JsonElement.ArrayEnumerator zonesEnumerator = info.GetProperty("zones").EnumerateArray(); - IList zones = new List(); - while (zonesEnumerator.MoveNext()) + i = 0; + JsonElement.ArrayEnumerator cellChildMapRows = info.GetProperty("cell-child-map").EnumerateArray(); + int[,] cellChildMap = new int[rows, columns]; + while (cellChildMapRows.MoveNext()) + { + int j = 0; + JsonElement.ArrayEnumerator cellChildMapRowElems = cellChildMapRows.Current.EnumerateArray(); + while (cellChildMapRowElems.MoveNext()) + { + cellChildMap[i, j++] = cellChildMapRowElems.Current.GetInt32(); + } + + i++; + } + + _customModels.Add(new GridLayoutModel(uuid, name, LayoutType.Custom, rows, columns, rowsPercentage, columnsPercentage, cellChildMap)); + } + else if (type.Equals("canvas")) { - int x = zonesEnumerator.Current.GetProperty("X").GetInt32(); - int y = zonesEnumerator.Current.GetProperty("Y").GetInt32(); - int width = zonesEnumerator.Current.GetProperty("width").GetInt32(); - int height = zonesEnumerator.Current.GetProperty("height").GetInt32(); - zones.Add(new Int32Rect(x, y, width, height)); - } + int referenceWidth = info.GetProperty("ref-width").GetInt32(); + int referenceHeight = info.GetProperty("ref-height").GetInt32(); + JsonElement.ArrayEnumerator zonesEnumerator = info.GetProperty("zones").EnumerateArray(); + IList zones = new List(); + while (zonesEnumerator.MoveNext()) + { + int x = zonesEnumerator.Current.GetProperty("X").GetInt32(); + int y = zonesEnumerator.Current.GetProperty("Y").GetInt32(); + int width = zonesEnumerator.Current.GetProperty("width").GetInt32(); + int height = zonesEnumerator.Current.GetProperty("height").GetInt32(); + zones.Add(new Int32Rect(x, y, width, height)); + } - _customModels.Add(new CanvasLayoutModel(uuid, name, LayoutType.Custom, referenceWidth, referenceHeight, zones)); + _customModels.Add(new CanvasLayoutModel(uuid, name, LayoutType.Custom, referenceWidth, referenceHeight, zones)); + } } + + inputStream.Close(); + } + catch (Exception ex) + { + ShowExceptionMessageBox("Error loading custom layouts", ex); + return new ObservableCollection(); } return _customModels; @@ -239,56 +252,62 @@ namespace FancyZonesEditor.Models public abstract LayoutModel Clone(); - public void Persist(System.Windows.Int32Rect[] zones) + public void Persist() { PersistData(); - Apply(zones); + Apply(); } - public void Apply(System.Windows.Int32Rect[] zones) + public void Apply() { - int zoneCount = zones.Length; - FileStream outputStream = File.Open(Settings.ActiveZoneSetTmpFile, FileMode.Create); - var writer = new Utf8JsonWriter(outputStream, options: default); - - writer.WriteStartObject(); - writer.WriteString("device-id", Settings.UniqueKey); - - writer.WriteStartObject("active-zoneset"); - writer.WriteString("uuid", "{" + Guid.ToString().ToUpper() + "}"); - switch (Type) + try { - case LayoutType.Focus: - writer.WriteString("type", "focus"); - break; - case LayoutType.Rows: - writer.WriteString("type", "rows"); - break; - case LayoutType.Columns: - writer.WriteString("type", "columns"); - break; - case LayoutType.Grid: - writer.WriteString("type", "grid"); - break; - case LayoutType.PriorityGrid: - writer.WriteString("type", "priority-grid"); - break; - case LayoutType.Custom: - writer.WriteString("type", "custom"); - break; + FileStream outputStream = File.Open(Settings.ActiveZoneSetTmpFile, FileMode.Create); + var writer = new Utf8JsonWriter(outputStream, options: default); + + writer.WriteStartObject(); + writer.WriteString("device-id", Settings.UniqueKey); + + writer.WriteStartObject("active-zoneset"); + writer.WriteString("uuid", "{" + Guid.ToString().ToUpper() + "}"); + switch (Type) + { + case LayoutType.Focus: + writer.WriteString("type", "focus"); + break; + case LayoutType.Rows: + writer.WriteString("type", "rows"); + break; + case LayoutType.Columns: + writer.WriteString("type", "columns"); + break; + case LayoutType.Grid: + writer.WriteString("type", "grid"); + break; + case LayoutType.PriorityGrid: + writer.WriteString("type", "priority-grid"); + break; + case LayoutType.Custom: + writer.WriteString("type", "custom"); + break; + } + + writer.WriteEndObject(); + + Settings settings = ((App)Application.Current).ZoneSettings; + + writer.WriteBoolean("editor-show-spacing", settings.ShowSpacing); + writer.WriteNumber("editor-spacing", settings.Spacing); + writer.WriteNumber("editor-zone-count", settings.ZoneCount); + + writer.WriteEndObject(); + writer.Flush(); + outputStream.Close(); + } + catch (Exception ex) + { + ShowExceptionMessageBox("Error applying layout", ex); } - - writer.WriteEndObject(); - - Settings settings = ((App)Application.Current).ZoneSettings; - - writer.WriteBoolean("editor-show-spacing", settings.ShowSpacing); - writer.WriteNumber("editor-spacing", settings.Spacing); - writer.WriteNumber("editor-zone-count", settings.ZoneCount); - - writer.WriteEndObject(); - writer.Flush(); - outputStream.Close(); } } } diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs index 037df109eb..3947ff6181 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs @@ -360,48 +360,56 @@ namespace FancyZonesEditor private void ParseDeviceInfoData() { - FileStream inputStream = File.Open(Settings.ActiveZoneSetTmpFile, FileMode.Open); - var jsonObject = JsonDocument.Parse(inputStream, options: default).RootElement; - - UniqueKey = jsonObject.GetProperty("device-id").GetString(); - ActiveZoneSetUUid = jsonObject.GetProperty("active-zoneset").GetProperty("uuid").GetString(); - string layoutType = jsonObject.GetProperty("active-zoneset").GetProperty("type").GetString(); - - if (ActiveZoneSetUUid == "null" || layoutType == "blank") + try { - // Default selection is Focus - ActiveZoneSetLayoutType = LayoutType.Focus; - _showSpacing = true; - _spacing = 16; - _zoneCount = 3; - } - else - { - switch (layoutType) + FileStream inputStream = File.Open(Settings.ActiveZoneSetTmpFile, FileMode.Open); + var jsonObject = JsonDocument.Parse(inputStream, options: default).RootElement; + + UniqueKey = jsonObject.GetProperty("device-id").GetString(); + ActiveZoneSetUUid = jsonObject.GetProperty("active-zoneset").GetProperty("uuid").GetString(); + string layoutType = jsonObject.GetProperty("active-zoneset").GetProperty("type").GetString(); + + if (ActiveZoneSetUUid == "null" || layoutType == "blank") { - case "focus": - ActiveZoneSetLayoutType = LayoutType.Focus; - break; - case "columns": - ActiveZoneSetLayoutType = LayoutType.Columns; - break; - case "rows": - ActiveZoneSetLayoutType = LayoutType.Rows; - break; - case "grid": - ActiveZoneSetLayoutType = LayoutType.Grid; - break; - case "priority-grid": - ActiveZoneSetLayoutType = LayoutType.PriorityGrid; - break; - case "custom": - ActiveZoneSetLayoutType = LayoutType.Custom; - break; + // Default selection is Focus + ActiveZoneSetLayoutType = LayoutType.Focus; + _showSpacing = true; + _spacing = 16; + _zoneCount = 3; + } + else + { + switch (layoutType) + { + case "focus": + ActiveZoneSetLayoutType = LayoutType.Focus; + break; + case "columns": + ActiveZoneSetLayoutType = LayoutType.Columns; + break; + case "rows": + ActiveZoneSetLayoutType = LayoutType.Rows; + break; + case "grid": + ActiveZoneSetLayoutType = LayoutType.Grid; + break; + case "priority-grid": + ActiveZoneSetLayoutType = LayoutType.PriorityGrid; + break; + case "custom": + ActiveZoneSetLayoutType = LayoutType.Custom; + break; + } + + _showSpacing = jsonObject.GetProperty("editor-show-spacing").GetBoolean(); + _spacing = jsonObject.GetProperty("editor-spacing").GetInt32(); + _zoneCount = jsonObject.GetProperty("editor-zone-count").GetInt32(); } - _showSpacing = jsonObject.GetProperty("editor-show-spacing").GetBoolean(); - _spacing = jsonObject.GetProperty("editor-spacing").GetInt32(); - _zoneCount = jsonObject.GetProperty("editor-zone-count").GetInt32(); + inputStream.Close(); + } catch (Exception ex) + { + LayoutModel.ShowExceptionMessageBox("Error parsing device info data", ex); } }