/ src / modules / cmdpal / ext / Microsoft.CmdPal.Ext.Bookmark / BookmarksCommandProvider.cs
BookmarksCommandProvider.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.Diagnostics.Contracts;
  6  using System.IO;
  7  using System.Linq;
  8  using System.Threading;
  9  using Microsoft.CmdPal.Ext.Bookmarks.Pages;
 10  using Microsoft.CmdPal.Ext.Bookmarks.Persistence;
 11  using Microsoft.CmdPal.Ext.Bookmarks.Services;
 12  using Microsoft.CommandPalette.Extensions;
 13  
 14  namespace Microsoft.CmdPal.Ext.Bookmarks;
 15  
 16  public sealed partial class BookmarksCommandProvider : CommandProvider
 17  {
 18      private const int LoadStateNotLoaded = 0;
 19      private const int LoadStateLoading = 1;
 20      private const int LoadStateLoaded = 2;
 21  
 22      private readonly IPlaceholderParser _placeholderParser = new PlaceholderParser();
 23      private readonly IBookmarksManager _bookmarksManager;
 24      private readonly IBookmarkResolver _commandResolver;
 25      private readonly IBookmarkIconLocator _iconLocator = new IconLocator();
 26  
 27      private readonly ListItem _addNewItem;
 28      private readonly Lock _bookmarksLock = new();
 29  
 30      private ICommandItem[] _commands = [];
 31      private List<BookmarkListItem> _bookmarks = [];
 32      private int _loadState;
 33  
 34      private static string StateJsonPath()
 35      {
 36          var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
 37          Directory.CreateDirectory(directory);
 38          return Path.Combine(directory, "bookmarks.json");
 39      }
 40  
 41      public static BookmarksCommandProvider CreateWithDefaultStore()
 42      {
 43          return new BookmarksCommandProvider(new BookmarksManager(new FileBookmarkDataSource(StateJsonPath())));
 44      }
 45  
 46      internal BookmarksCommandProvider(IBookmarksManager bookmarksManager)
 47      {
 48          ArgumentNullException.ThrowIfNull(bookmarksManager);
 49          _bookmarksManager = bookmarksManager;
 50          _bookmarksManager.BookmarkAdded += OnBookmarkAdded;
 51          _bookmarksManager.BookmarkRemoved += OnBookmarkRemoved;
 52  
 53          _commandResolver = new BookmarkResolver(_placeholderParser);
 54  
 55          Id = "Bookmarks";
 56          DisplayName = Resources.bookmarks_display_name;
 57          Icon = Icons.PinIcon;
 58  
 59          var addBookmarkPage = new AddBookmarkPage(null);
 60          addBookmarkPage.AddedCommand += (_, e) => _bookmarksManager.Add(e.Name, e.Bookmark);
 61          _addNewItem = new ListItem(addBookmarkPage);
 62      }
 63  
 64      private void OnBookmarkAdded(BookmarkData bookmarkData)
 65      {
 66          var newItem = new BookmarkListItem(bookmarkData, _bookmarksManager, _commandResolver, _iconLocator, _placeholderParser);
 67          lock (_bookmarksLock)
 68          {
 69              _bookmarks.Add(newItem);
 70          }
 71  
 72          NotifyChange();
 73      }
 74  
 75      private void OnBookmarkRemoved(BookmarkData bookmarkData)
 76      {
 77          lock (_bookmarksLock)
 78          {
 79              _bookmarks.RemoveAll(t => t.BookmarkId == bookmarkData.Id);
 80          }
 81  
 82          NotifyChange();
 83      }
 84  
 85      public override ICommandItem[] TopLevelCommands()
 86      {
 87          if (Volatile.Read(ref _loadState) != LoadStateLoaded)
 88          {
 89              if (Interlocked.CompareExchange(ref _loadState, LoadStateLoading, LoadStateNotLoaded) == LoadStateNotLoaded)
 90              {
 91                  try
 92                  {
 93                      lock (_bookmarksLock)
 94                      {
 95                          _bookmarks = [.. _bookmarksManager.Bookmarks.Select(bookmark => new BookmarkListItem(bookmark, _bookmarksManager, _commandResolver, _iconLocator, _placeholderParser))];
 96                          _commands = BuildTopLevelCommandsUnsafe();
 97                      }
 98  
 99                      Volatile.Write(ref _loadState, LoadStateLoaded);
100                      RaiseItemsChanged();
101                  }
102                  catch
103                  {
104                      Volatile.Write(ref _loadState, LoadStateNotLoaded);
105                      throw;
106                  }
107              }
108          }
109  
110          return _commands;
111      }
112  
113      private void NotifyChange()
114      {
115          if (Volatile.Read(ref _loadState) != LoadStateLoaded)
116          {
117              return;
118          }
119  
120          lock (_bookmarksLock)
121          {
122              _commands = BuildTopLevelCommandsUnsafe();
123          }
124  
125          RaiseItemsChanged();
126      }
127  
128      [Pure]
129      private ICommandItem[] BuildTopLevelCommandsUnsafe() => [_addNewItem, .. _bookmarks];
130  }