Skip to content

Commit 0b509c0

Browse files
committed
feat: cache SVG previews in File Explorer
1 parent 9a55209 commit 0b509c0

5 files changed

Lines changed: 97 additions & 43 deletions

File tree

src/modules/previewpane/SvgPreviewHandler/SvgPreviewControl.cs

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The Microsoft Corporation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using System.IO;
56
using System.Net.Http;
67
using System.Reflection;
78
using System.Runtime.CompilerServices;
@@ -106,7 +107,7 @@ public override void DoPreview<T>(T dataSource)
106107
return;
107108
}
108109

109-
CleanupWebView2UserDataFolder();
110+
EnsureWebView2UserDataFolder();
110111

111112
string svgData = null;
112113
bool blocked = false;
@@ -251,23 +252,27 @@ private void AddWebViewControl(string svgData)
251252
_browser.CoreWebView2.AddWebResourceRequestedFilter("*", CoreWebView2WebResourceContext.All);
252253
_browser.CoreWebView2.WebResourceRequested += CoreWebView2_BlockExternalResources;
253254

254-
string generatedPreview = _previewGenerator.GeneratePreview(svgData);
255+
var cacheKey = SvgPreviewCacheHelper.BuildCacheKey(
256+
"v1",
257+
VirtualHostName,
258+
svgData,
259+
_settings.ColorMode.ToString(),
260+
_settings.ThemeColor.ToArgb().ToString(),
261+
_settings.SolidColor.ToArgb().ToString(),
262+
_settings.CheckeredShade.ToString());
255263

256-
// WebView2.NavigateToString() limitation
257-
// See https://learn.microsoft.com/dotnet/api/microsoft.web.webview2.core.corewebview2.navigatetostring?view=webview2-dotnet-1.0.864.35#remarks
258-
// While testing the limit, it turned out it is ~1.5MB, so to be on a safe side we go for 1.5m bytes
259-
if (generatedPreview.Length > 1_500_000)
260-
{
261-
string filename = _webView2UserDataFolder + "\\" + Guid.NewGuid().ToString() + ".html";
262-
File.WriteAllText(filename, generatedPreview);
263-
_localFileURI = new Uri(filename);
264-
_browser.Source = _localFileURI;
265-
}
266-
else
264+
var cacheFolder = Path.Combine(_webView2UserDataFolder, "Cache");
265+
var cacheFilePath = SvgPreviewCacheHelper.GetCacheFilePath(cacheFolder, cacheKey);
266+
267+
if (!File.Exists(cacheFilePath) || new FileInfo(cacheFilePath).Length == 0)
267268
{
268-
_browser.NavigateToString(generatedPreview);
269+
string generatedPreview = _previewGenerator.GeneratePreview(svgData);
270+
File.WriteAllText(cacheFilePath, generatedPreview);
269271
}
270272

273+
_localFileURI = new Uri(cacheFilePath);
274+
_browser.Source = _localFileURI;
275+
271276
Controls.Add(_browser);
272277
}
273278
catch (Exception)
@@ -318,17 +323,11 @@ private void PreviewError<T>(Exception exception, T dataSource)
318323
/// <summary>
319324
/// Cleanup the previously created tmp html files from svg files bigger than 2MB.
320325
/// </summary>
321-
private void CleanupWebView2UserDataFolder()
326+
private void EnsureWebView2UserDataFolder()
322327
{
323328
try
324329
{
325-
// Cleanup temp dir
326-
var dir = new DirectoryInfo(_webView2UserDataFolder);
327-
328-
foreach (var file in dir.EnumerateFiles("*.html"))
329-
{
330-
file.Delete();
331-
}
330+
Directory.CreateDirectory(_webView2UserDataFolder);
332331
}
333332
catch (Exception)
334333
{

src/modules/previewpane/SvgThumbnailProvider/SvgThumbnailProvider.cs

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ private static string AssemblyDirectory
100100
/// <param name="cx">The maximum thumbnail size, in pixels.</param>
101101
public Bitmap GetThumbnailImpl(uint cx)
102102
{
103-
CleanupWebView2UserDataFolder();
103+
EnsureWebView2UserDataFolder();
104104

105105
if (cx == 0 || cx > MaxThumbnailSize)
106106
{
@@ -177,27 +177,24 @@ public Bitmap GetThumbnailImpl(uint cx)
177177
}
178178
};
179179

180-
// WebView2.NavigateToString() limitation
181-
// See https://learn.microsoft.com/dotnet/api/microsoft.web.webview2.core.corewebview2.navigatetostring?view=webview2-dotnet-1.0.864.35#remarks
182-
// While testing the limit, it turned out it is ~1.5MB, so to be on a safe side we go for 1.5m bytes
183180
SvgContentsReady.Wait();
184181
if (string.IsNullOrEmpty(SvgContents) || !SvgContents.Contains("svg"))
185182
{
186183
thumbnailDone.Set();
187184
return;
188185
}
189186

190-
if (SvgContents.Length > 1_500_000)
191-
{
192-
string filename = _webView2UserDataFolder + "\\" + Guid.NewGuid().ToString() + ".html";
193-
File.WriteAllText(filename, SvgContents);
194-
_localFileURI = new Uri(filename);
195-
_browser.Source = _localFileURI;
196-
}
197-
else
187+
var cacheKey = SvgPreviewCacheHelper.BuildCacheKey("v1", VirtualHostName, SvgContents);
188+
var cacheFolder = Path.Combine(_webView2UserDataFolder, "Cache");
189+
var cacheFilePath = SvgPreviewCacheHelper.GetCacheFilePath(cacheFolder, cacheKey);
190+
191+
if (!File.Exists(cacheFilePath) || new FileInfo(cacheFilePath).Length == 0)
198192
{
199-
_browser.NavigateToString(SvgContents);
193+
File.WriteAllText(cacheFilePath, SvgContents);
200194
}
195+
196+
_localFileURI = new Uri(cacheFilePath);
197+
_browser.Source = _localFileURI;
201198
}
202199
catch (Exception ex)
203200
{
@@ -344,17 +341,11 @@ public void Dispose()
344341
/// <summary>
345342
/// Cleanup the previously created tmp html files from svg files bigger than 2MB.
346343
/// </summary>
347-
private void CleanupWebView2UserDataFolder()
344+
private void EnsureWebView2UserDataFolder()
348345
{
349346
try
350347
{
351-
// Cleanup temp dir
352-
var dir = new DirectoryInfo(_webView2UserDataFolder);
353-
354-
foreach (var file in dir.EnumerateFiles("*.html"))
355-
{
356-
file.Delete();
357-
}
348+
Directory.CreateDirectory(_webView2UserDataFolder);
358349
}
359350
catch (Exception)
360351
{

src/modules/previewpane/UnitTests-SvgPreviewHandler/SvgPreviewHandlerHelperTests.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,5 +99,31 @@ public void CheckBlockedElementsShouldReturnFalseIfSvgDataIsNullOrWhiteSpaces(st
9999
// Assert
100100
Assert.IsFalse(foundFilteredElement);
101101
}
102+
103+
[TestMethod]
104+
public void BuildCacheKeyShouldReturnSameValueForSameInputs()
105+
{
106+
// Arrange
107+
var firstKey = SvgPreviewCacheHelper.BuildCacheKey("v1", "svg-preview", "sample data");
108+
109+
// Act
110+
var secondKey = SvgPreviewCacheHelper.BuildCacheKey("v1", "svg-preview", "sample data");
111+
112+
// Assert
113+
Assert.AreEqual(firstKey, secondKey);
114+
}
115+
116+
[TestMethod]
117+
public void BuildCacheKeyShouldReturnDifferentValueForDifferentInputs()
118+
{
119+
// Arrange
120+
var firstKey = SvgPreviewCacheHelper.BuildCacheKey("v1", "svg-preview", "sample data");
121+
122+
// Act
123+
var secondKey = SvgPreviewCacheHelper.BuildCacheKey("v1", "svg-preview", "different data");
124+
125+
// Assert
126+
Assert.AreNotEqual(firstKey, secondKey);
127+
}
102128
}
103129
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
using System.Runtime.CompilerServices;
2+
3+
[assembly: InternalsVisibleTo("PowerToys.SvgPreviewHandler")]
4+
[assembly: InternalsVisibleTo("PowerToys.SvgThumbnailProvider")]
5+
[assembly: InternalsVisibleTo("Preview.SvgPreviewHandler.UnitTests")]
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright (c) Microsoft Corporation
2+
// The Microsoft Corporation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.IO;
7+
using System.Security.Cryptography;
8+
using System.Text;
9+
10+
namespace Common.Utilities
11+
{
12+
internal static class SvgPreviewCacheHelper
13+
{
14+
internal static string BuildCacheKey(params string[] cacheInputs)
15+
{
16+
var cacheKeyBuilder = new StringBuilder();
17+
18+
foreach (var input in cacheInputs)
19+
{
20+
cacheKeyBuilder.Append(input ?? string.Empty);
21+
cacheKeyBuilder.Append('\n');
22+
}
23+
24+
return Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(cacheKeyBuilder.ToString())));
25+
}
26+
27+
internal static string GetCacheFilePath(string cacheRootFolder, string cacheKey)
28+
{
29+
Directory.CreateDirectory(cacheRootFolder);
30+
return Path.Combine(cacheRootFolder, $"{cacheKey}.html");
31+
}
32+
}
33+
}

0 commit comments

Comments
 (0)