From d61dba5953fe40a1036f6d8358e09ed3d96a9094 Mon Sep 17 00:00:00 2001 From: Steven Cohn Date: Wed, 29 Nov 2023 15:22:50 -0500 Subject: [PATCH] Optimize the save performance of page elements #1122 --- .../Extensions/HashAlgorithmExtensions.cs | 21 +++++++ OneMore/Models/Page.cs | 57 ++++++++++++++++++- OneMore/OneMore.csproj | 1 + OneMore/OneNote.cs | 19 ++----- 4 files changed, 81 insertions(+), 17 deletions(-) create mode 100644 OneMore/Helpers/Extensions/HashAlgorithmExtensions.cs diff --git a/OneMore/Helpers/Extensions/HashAlgorithmExtensions.cs b/OneMore/Helpers/Extensions/HashAlgorithmExtensions.cs new file mode 100644 index 0000000000..8f359d2c1d --- /dev/null +++ b/OneMore/Helpers/Extensions/HashAlgorithmExtensions.cs @@ -0,0 +1,21 @@ +//************************************************************************************************ +// Copyright © 2032 Steven M Cohn. All rights reserved. +//************************************************************************************************ + +namespace River.OneMoreAddIn +{ + using System.Numerics; + using System.Security.Cryptography; + using System.Text; + + + internal static class HashAlgorithmExtensions + { + public static string GetHashString(this HashAlgorithm algorithm, string s) + { + var data = Encoding.UTF8.GetBytes(s); + var hash = algorithm.ComputeHash(data); + return new BigInteger(hash).ToString(); + } + } +} diff --git a/OneMore/Models/Page.cs b/OneMore/Models/Page.cs index f84fc28809..a4c80bc613 100644 --- a/OneMore/Models/Page.cs +++ b/OneMore/Models/Page.cs @@ -17,7 +17,7 @@ namespace River.OneMoreAddIn.Models using System.Globalization; using System.Linq; using System.Media; - using System.Security.Policy; + using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Xml; @@ -31,6 +31,8 @@ public partial class Page { public int TopOutlinePosition = 86; + private const string HashAttributeName = "omHash"; + /// /// Initialize a new instance with the given page XML root @@ -40,16 +42,65 @@ public Page(XElement root) { if (root != null) { - Root = root; Namespace = root.GetNamespaceOfPrefix(OneNote.Prefix); - PageId = root.Attribute("ID")?.Value; + ComputeHashes(root); + + Root = root; } SelectionScope = SelectionScope.Unknown; } + #region Hashing + /// + /// Calculates a hash value for each 1st gen child element on the page, which will + /// be used to optimize the page just prior to saving. + /// + /// The root element of the page + private void ComputeHashes(XElement root) + { + using var algo = MD5.Create(); + + // 1st generation child elements of the Page + foreach (var child in root.Elements()) + { + child.Add(new XAttribute( + HashAttributeName, + algo.GetHashString(child.ToString(SaveOptions.DisableFormatting)) + )); + } + } + + + /// + /// Keeps only modified 1st gen child elements of the page to optimize save performance + /// especially when the page is loaded with many Ink drawings. + /// + public void OptimizeForSave() + { + // MD5 should be sufficient and performs better than any other algorithm + using var algo = MD5.Create(); + + // 1st generation child elements of the Page + foreach (var child in Root.Elements().ToList()) + { + var att = child.Attribute(HashAttributeName); + if (att != null) + { + att.Remove(); + var hash = algo.GetHashString(child.ToString(SaveOptions.DisableFormatting)); + if (hash == att.Value) + { + child.Remove(); + } + } + } + } + #endregion Hashing + + public bool IsValid => Root != null; diff --git a/OneMore/OneMore.csproj b/OneMore/OneMore.csproj index 70b20e907b..973f5b6f37 100644 --- a/OneMore/OneMore.csproj +++ b/OneMore/OneMore.csproj @@ -241,6 +241,7 @@ + diff --git a/OneMore/OneNote.cs b/OneMore/OneNote.cs index dc31e8a651..8d2f03eda1 100644 --- a/OneMore/OneNote.cs +++ b/OneMore/OneNote.cs @@ -1047,22 +1047,13 @@ public async Task Update(Page page) return; } - await Update(page.Root); - } + // must optimize before we can validate schema... + page.OptimizeForSave(); - /// - /// Updates the given content, with a unique ID, on the current page. - /// - /// A page or element within a page with a unique objectID - public async Task Update(XElement element) - { - if (element.Name.LocalName == "Page") + if (!ValidateSchema(page.Root)) { - if (!ValidateSchema(element)) - { - return; - } + return; } // dateExpectedLastModified is merely a pessimistic-locking safeguard to prevent @@ -1072,7 +1063,7 @@ public async Task Update(XElement element) // ? DateTime.Parse(att.Value).ToUniversalTime() // : DateTime.MinValue; - var xml = element.ToString(SaveOptions.DisableFormatting); + var xml = page.Root.ToString(SaveOptions.DisableFormatting); await InvokeWithRetry(() => {