/ src / modules / cmdpal / Microsoft.CmdPal.UI.ViewModels / Services / DefaultCommandProviderCache.cs
DefaultCommandProviderCache.cs
  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.Text.Json;
  6  using ManagedCommon;
  7  using Microsoft.CmdPal.Core.Common.Helpers;
  8  using Microsoft.CommandPalette.Extensions.Toolkit;
  9  
 10  namespace Microsoft.CmdPal.UI.ViewModels.Services;
 11  
 12  public sealed partial class DefaultCommandProviderCache : ICommandProviderCache, IDisposable
 13  {
 14      private const string CacheFileName = "commandProviderCache.json";
 15  
 16      private readonly Dictionary<string, CommandProviderCacheItem> _cache = new(StringComparer.Ordinal);
 17  
 18      private readonly Lock _sync = new();
 19  
 20      private readonly SupersedingAsyncGate _saveGate;
 21  
 22      public DefaultCommandProviderCache()
 23      {
 24          _saveGate = new SupersedingAsyncGate(async _ => await TrySaveAsync().ConfigureAwait(false));
 25          TryLoad();
 26      }
 27  
 28      public void Memorize(string providerId, CommandProviderCacheItem item)
 29      {
 30          ArgumentNullException.ThrowIfNull(providerId);
 31  
 32          lock (_sync)
 33          {
 34              _cache[providerId] = item;
 35          }
 36  
 37          _ = _saveGate.ExecuteAsync();
 38      }
 39  
 40      public CommandProviderCacheItem? Recall(string providerId)
 41      {
 42          ArgumentNullException.ThrowIfNull(providerId);
 43  
 44          lock (_sync)
 45          {
 46              _cache.TryGetValue(providerId, out var item);
 47              return item;
 48          }
 49      }
 50  
 51      private static string GetCacheFilePath()
 52      {
 53          var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
 54          Directory.CreateDirectory(directory);
 55          return Path.Combine(directory, CacheFileName);
 56      }
 57  
 58      private void TryLoad()
 59      {
 60          try
 61          {
 62              var path = GetCacheFilePath();
 63              if (!File.Exists(path))
 64              {
 65                  return;
 66              }
 67  
 68              var json = File.ReadAllText(path);
 69              if (string.IsNullOrWhiteSpace(json))
 70              {
 71                  return;
 72              }
 73  
 74              var loaded = JsonSerializer.Deserialize(
 75                  json,
 76                  CommandProviderCacheSerializationContext.Default.CommandProviderCacheContainer!);
 77              if (loaded?.Cache is null)
 78              {
 79                  return;
 80              }
 81  
 82              _cache.Clear();
 83              foreach (var kvp in loaded.Cache)
 84              {
 85                  if (!string.IsNullOrEmpty(kvp.Key) && kvp.Value is not null)
 86                  {
 87                      _cache[kvp.Key] = kvp.Value;
 88                  }
 89              }
 90          }
 91          catch (Exception ex)
 92          {
 93              Logger.LogError("Failed to load command provider cache: ", ex);
 94          }
 95      }
 96  
 97      private async Task TrySaveAsync()
 98      {
 99          try
100          {
101              Dictionary<string, CommandProviderCacheItem> snapshot;
102              lock (_sync)
103              {
104                  snapshot = new Dictionary<string, CommandProviderCacheItem>(_cache, StringComparer.Ordinal);
105              }
106  
107              var container = new CommandProviderCacheContainer
108              {
109                  Cache = snapshot,
110              };
111  
112              var path = GetCacheFilePath();
113              var json = JsonSerializer.Serialize(container, CommandProviderCacheSerializationContext.Default.CommandProviderCacheContainer!);
114              await File.WriteAllTextAsync(path, json).ConfigureAwait(false);
115          }
116          catch (Exception ex)
117          {
118              Logger.LogError("Failed to save command provider cache: ", ex);
119          }
120      }
121  
122      public void Dispose()
123      {
124          _saveGate.Dispose();
125          GC.SuppressFinalize(this);
126      }
127  }