CommandViewModel.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 Microsoft.CmdPal.Core.ViewModels.Models;
  6  using Microsoft.CommandPalette.Extensions;
  7  
  8  namespace Microsoft.CmdPal.Core.ViewModels;
  9  
 10  public partial class CommandViewModel : ExtensionObjectViewModel
 11  {
 12      public ExtensionObject<ICommand> Model { get; private set; } = new(null);
 13  
 14      protected bool IsInitialized { get; private set; }
 15  
 16      protected bool IsFastInitialized { get; private set; }
 17  
 18      public bool HasIcon => Icon.IsSet;
 19  
 20      // These are properties that are "observable" from the extension object
 21      // itself, in the sense that they get raised by PropChanged events from the
 22      // extension. However, we don't want to actually make them
 23      // [ObservableProperty]s, because PropChanged comes in off the UI thread,
 24      // and ObservableProperty is not smart enough to raise the PropertyChanged
 25      // on the UI thread.
 26      public string Id { get; private set; } = string.Empty;
 27  
 28      public string Name { get; private set; } = string.Empty;
 29  
 30      public IconInfoViewModel Icon { get; private set; }
 31  
 32      // UNDER NO CIRCUMSTANCES MAY SOMEONE WRITE TO THIS DICTIONARY.
 33      // This is our copy of the data from the extension.
 34      // Adding values to it does not add to the extension.
 35      // Modifying it will not modify the extension
 36      // (except it might, if the dictionary was passed by ref)
 37      private Dictionary<string, ExtensionObject<object>>? _properties;
 38  
 39      public IReadOnlyDictionary<string, ExtensionObject<object>>? Properties => _properties?.AsReadOnly();
 40  
 41      public CommandViewModel(ICommand? command, WeakReference<IPageContext> pageContext)
 42          : base(pageContext)
 43      {
 44          Model = new(command);
 45          Icon = new(null);
 46      }
 47  
 48      public void FastInitializeProperties()
 49      {
 50          if (IsFastInitialized)
 51          {
 52              return;
 53          }
 54  
 55          var model = Model.Unsafe;
 56          if (model is null)
 57          {
 58              return;
 59          }
 60  
 61          Id = model.Id ?? string.Empty;
 62          Name = model.Name ?? string.Empty;
 63          IsFastInitialized = true;
 64      }
 65  
 66      public override void InitializeProperties()
 67      {
 68          if (IsInitialized)
 69          {
 70              return;
 71          }
 72  
 73          if (!IsFastInitialized)
 74          {
 75              FastInitializeProperties();
 76          }
 77  
 78          var model = Model.Unsafe;
 79          if (model is null)
 80          {
 81              return;
 82          }
 83  
 84          var ico = model.Icon;
 85          if (ico is not null)
 86          {
 87              Icon = new(ico);
 88              Icon.InitializeProperties();
 89              UpdateProperty(nameof(Icon));
 90          }
 91  
 92          if (model is IExtendedAttributesProvider command2)
 93          {
 94              UpdatePropertiesFromExtension(command2);
 95          }
 96  
 97          model.PropChanged += Model_PropChanged;
 98      }
 99  
100      private void Model_PropChanged(object sender, IPropChangedEventArgs args)
101      {
102          try
103          {
104              FetchProperty(args.PropertyName);
105          }
106          catch (Exception ex)
107          {
108              ShowException(ex, Name);
109          }
110      }
111  
112      protected void FetchProperty(string propertyName)
113      {
114          var model = Model.Unsafe;
115          if (model is null)
116          {
117              return; // throw?
118          }
119  
120          switch (propertyName)
121          {
122              case nameof(Name):
123                  Name = model.Name;
124                  break;
125              case nameof(Icon):
126                  var iconInfo = model.Icon;
127                  Icon = new(iconInfo);
128                  Icon.InitializeProperties();
129                  break;
130          }
131  
132          UpdateProperty(propertyName);
133      }
134  
135      protected override void UnsafeCleanup()
136      {
137          base.UnsafeCleanup();
138  
139          Icon = new(null); // necessary?
140  
141          var model = Model.Unsafe;
142          if (model is not null)
143          {
144              model.PropChanged -= Model_PropChanged;
145          }
146      }
147  
148      private void UpdatePropertiesFromExtension(IExtendedAttributesProvider? model)
149      {
150          var propertiesFromExtension = model?.GetProperties();
151          if (propertiesFromExtension == null)
152          {
153              _properties = null;
154              return;
155          }
156  
157          _properties = [];
158  
159          // COPY the properties into us.
160          // The IDictionary that was passed to us may be marshalled by-ref or by-value, we _don't know_.
161          //
162          // If it's by-ref, the values are arbitrary objects that are out-of-proc.
163          // If it's bu-value, then everything is in-proc, and we can't mutate the data.
164          foreach (var property in propertiesFromExtension)
165          {
166              _properties.Add(property.Key, new(property.Value));
167          }
168      }
169  }