Skip to content

Commit

Permalink
Optimize the save performance of page elements
Browse files Browse the repository at this point in the history
  • Loading branch information
stevencohn authored and weissm committed Dec 5, 2023
1 parent ef3fc49 commit a6be3bb
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 17 deletions.
21 changes: 21 additions & 0 deletions OneMore/Helpers/Extensions/HashAlgorithmExtensions.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
57 changes: 54 additions & 3 deletions OneMore/Models/Page.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -31,6 +31,8 @@ public partial class Page
{
public int TopOutlinePosition = 86;

private const string HashAttributeName = "omHash";


/// <summary>
/// Initialize a new instance with the given page XML root
Expand All @@ -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
/// <summary>
/// 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.
/// </summary>
/// <param name="root">The root element of the page</param>
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))
));
}
}


/// <summary>
/// Keeps only modified 1st gen child elements of the page to optimize save performance
/// especially when the page is loaded with many Ink drawings.
/// </summary>
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;


Expand Down
1 change: 1 addition & 0 deletions OneMore/OneMore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@
<Compile Include="Helpers\ColorHelper.cs" />
<Compile Include="Helpers\Extensions\FontFamilyExtensions.cs" />
<Compile Include="Helpers\Extensions\GraphicsExtensions.cs" />
<Compile Include="Helpers\Extensions\HashAlgorithmExtensions.cs" />
<Compile Include="Helpers\Extensions\ScreenExtensions.cs" />
<Compile Include="Models\KnownSchemaAttributes.cs" />
<Compile Include="Models\PageReader.cs" />
Expand Down
19 changes: 5 additions & 14 deletions OneMore/OneNote.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1047,22 +1047,13 @@ public async Task Update(Page page)
return;
}

await Update(page.Root);
}
// must optimize before we can validate schema...

page.OptimizeForSave();

/// <summary>
/// Updates the given content, with a unique ID, on the current page.
/// </summary>
/// <param name="element">A page or element within a page with a unique objectID</param>
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
Expand All @@ -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(() =>
{
Expand Down

0 comments on commit a6be3bb

Please sign in to comment.