MainViewModel.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;
  6  using System.Collections.Generic;
  7  using System.Collections.ObjectModel;
  8  using System.ComponentModel;
  9  using System.Globalization;
 10  using System.Linq;
 11  using System.Threading.Tasks;
 12  
 13  using CommunityToolkit.Mvvm.ComponentModel;
 14  using EnvironmentVariablesUILib.Helpers;
 15  using EnvironmentVariablesUILib.Models;
 16  using EnvironmentVariablesUILib.Telemetry;
 17  using Microsoft.UI.Dispatching;
 18  
 19  namespace EnvironmentVariablesUILib.ViewModels
 20  {
 21      public partial class MainViewModel : ObservableObject
 22      {
 23          private readonly IEnvironmentVariablesService _environmentVariablesService;
 24  
 25          private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
 26  
 27          public DefaultVariablesSet UserDefaultSet { get; private set; } = new DefaultVariablesSet(VariablesSet.UserGuid, ResourceLoaderInstance.ResourceLoader.GetString("User"), VariablesSetType.User);
 28  
 29          public DefaultVariablesSet SystemDefaultSet { get; private set; } = new DefaultVariablesSet(VariablesSet.SystemGuid, ResourceLoaderInstance.ResourceLoader.GetString("System"), VariablesSetType.System);
 30  
 31          public VariablesSet DefaultVariables { get; private set; } = new DefaultVariablesSet(Guid.NewGuid(), "DefaultVariables", VariablesSetType.User);
 32  
 33          [ObservableProperty]
 34          private ObservableCollection<ProfileVariablesSet> _profiles;
 35  
 36          [ObservableProperty]
 37          private ObservableCollection<Variable> _appliedVariables = new ObservableCollection<Variable>();
 38  
 39          [ObservableProperty]
 40          private bool _isElevated;
 41  
 42          [ObservableProperty]
 43          [NotifyPropertyChangedFor(nameof(IsInfoBarButtonVisible))]
 44          private EnvironmentState _environmentState;
 45  
 46          public bool IsInfoBarButtonVisible => EnvironmentState == EnvironmentState.EnvironmentMessageReceived;
 47  
 48          public ProfileVariablesSet AppliedProfile { get; set; }
 49  
 50          public MainViewModel(IElevationHelper elevationHelper, IEnvironmentVariablesService environmentVariablesService, ILogger logger, ITelemetry telemetry)
 51          {
 52              _environmentVariablesService = environmentVariablesService;
 53  
 54              ElevationHelper.ElevationHelperInstance = elevationHelper;
 55              LoggerInstance.Logger = logger;
 56              TelemetryInstance.Telemetry = telemetry;
 57  
 58              var isElevated = ElevationHelper.ElevationHelperInstance.IsElevated;
 59              IsElevated = isElevated;
 60          }
 61  
 62          private void LoadDefaultVariables()
 63          {
 64              UserDefaultSet.Variables.Clear();
 65              SystemDefaultSet.Variables.Clear();
 66              DefaultVariables.Variables.Clear();
 67  
 68              EnvironmentVariablesHelper.GetVariables(EnvironmentVariableTarget.Machine, SystemDefaultSet);
 69              EnvironmentVariablesHelper.GetVariables(EnvironmentVariableTarget.User, UserDefaultSet);
 70  
 71              foreach (var variable in UserDefaultSet.Variables)
 72              {
 73                  DefaultVariables.Variables.Add(variable);
 74                  if (AppliedProfile != null)
 75                  {
 76                      if (AppliedProfile.Variables.Where(
 77                          x => (x.Name.Equals(variable.Name, StringComparison.OrdinalIgnoreCase) && x.Values.Equals(variable.Values, StringComparison.OrdinalIgnoreCase))
 78                              || variable.Name.Equals(EnvironmentVariablesHelper.GetBackupVariableName(x, AppliedProfile.Name), StringComparison.OrdinalIgnoreCase)).Any())
 79                      {
 80                          // If it's a user variable that's also in the profile or is a backup variable, mark it as applied from profile.
 81                          variable.IsAppliedFromProfile = true;
 82                      }
 83                  }
 84              }
 85  
 86              foreach (var variable in SystemDefaultSet.Variables)
 87              {
 88                  DefaultVariables.Variables.Add(variable);
 89              }
 90          }
 91  
 92          public void LoadEnvironmentVariables()
 93          {
 94              LoadDefaultVariables();
 95              LoadProfiles();
 96              PopulateAppliedVariables();
 97          }
 98  
 99          private void LoadProfiles()
100          {
101              try
102              {
103                  var profiles = _environmentVariablesService.ReadProfiles();
104                  foreach (var profile in profiles)
105                  {
106                      profile.PropertyChanged += Profile_PropertyChanged;
107  
108                      foreach (var variable in profile.Variables)
109                      {
110                          variable.ParentType = VariablesSetType.Profile;
111                      }
112                  }
113  
114                  var appliedProfiles = profiles.Where(x => x.IsEnabled).ToList();
115                  if (appliedProfiles.Count > 0)
116                  {
117                      var appliedProfile = appliedProfiles.First();
118                      if (appliedProfile.IsCorrectlyApplied())
119                      {
120                          AppliedProfile = appliedProfile;
121                          EnvironmentState = EnvironmentState.Unchanged;
122                      }
123                      else
124                      {
125                          EnvironmentState = EnvironmentState.ChangedOnStartup;
126                          appliedProfile.IsEnabled = false;
127                      }
128                  }
129  
130                  Profiles = new ObservableCollection<ProfileVariablesSet>(profiles);
131              }
132              catch (Exception ex)
133              {
134                  // Show some error
135                  LoggerInstance.Logger.LogError("Failed to load profiles.json file", ex);
136  
137                  Profiles = new ObservableCollection<ProfileVariablesSet>();
138              }
139          }
140  
141          private void PopulateAppliedVariables()
142          {
143              LoadDefaultVariables();
144  
145              var variables = new List<Variable>();
146              if (AppliedProfile != null)
147              {
148                  variables = variables.Concat(AppliedProfile.Variables.Select(x => new Variable(x.Name, Environment.ExpandEnvironmentVariables(x.Values), VariablesSetType.Profile)).OrderBy(x => x.Name)).ToList();
149              }
150  
151              // Variables are expanded to be shown in the applied variables section, so the user sees their actual values.
152              variables = variables.Concat(UserDefaultSet.Variables.Select(x => new Variable(x.Name, Environment.ExpandEnvironmentVariables(x.Values), VariablesSetType.User)).OrderBy(x => x.Name))
153                                   .Concat(SystemDefaultSet.Variables.Select(x => new Variable(x.Name, Environment.ExpandEnvironmentVariables(x.Values), VariablesSetType.System)).OrderBy(x => x.Name))
154                                   .ToList();
155  
156              // Handle PATH variable - add USER value to the end of the SYSTEM value
157              var profilePath = variables.Where(x => x.Name.Equals("PATH", StringComparison.OrdinalIgnoreCase) && x.ParentType == VariablesSetType.Profile).FirstOrDefault();
158              var userPath = variables.Where(x => x.Name.Equals("PATH", StringComparison.OrdinalIgnoreCase) && x.ParentType == VariablesSetType.User).FirstOrDefault();
159              var systemPath = variables.Where(x => x.Name.Equals("PATH", StringComparison.OrdinalIgnoreCase) && x.ParentType == VariablesSetType.System).FirstOrDefault();
160  
161              if (systemPath != null)
162              {
163                  var clone = systemPath.Clone();
164                  clone.ParentType = VariablesSetType.Path;
165  
166                  if (userPath != null)
167                  {
168                      clone.Values += ";" + userPath.Values;
169                      variables.Remove(userPath);
170                  }
171  
172                  if (profilePath != null)
173                  {
174                      variables.Remove(profilePath);
175                  }
176  
177                  variables.Insert(variables.IndexOf(systemPath), clone);
178                  variables.Remove(systemPath);
179              }
180  
181              variables = variables.GroupBy(x => x.Name).Select(y => y.First()).ToList();
182  
183              // Find duplicates
184              var duplicates = variables.Where(x => !x.Name.Equals("PATH", StringComparison.OrdinalIgnoreCase)).GroupBy(x => x.Name.ToLower(CultureInfo.InvariantCulture)).Where(g => g.Count() > 1);
185              foreach (var duplicate in duplicates)
186              {
187                  var userVar = duplicate.ElementAt(0);
188                  var systemVar = duplicate.ElementAt(1);
189  
190                  var clone = userVar.Clone();
191                  clone.ParentType = VariablesSetType.Duplicate;
192                  clone.Name = systemVar.Name;
193                  variables.Insert(variables.IndexOf(userVar), clone);
194                  variables.Remove(userVar);
195                  variables.Remove(systemVar);
196              }
197  
198              variables = variables.OrderBy(x => x.ParentType).ToList();
199              AppliedVariables = new ObservableCollection<Variable>(variables);
200          }
201  
202          internal void AddDefaultVariable(Variable variable, VariablesSetType type)
203          {
204              if (type == VariablesSetType.User)
205              {
206                  UserDefaultSet.Variables.Add(variable);
207                  UserDefaultSet.Variables = new ObservableCollection<Variable>(UserDefaultSet.Variables.OrderBy(x => x.Name).ToList());
208              }
209              else if (type == VariablesSetType.System)
210              {
211                  SystemDefaultSet.Variables.Add(variable);
212                  SystemDefaultSet.Variables = new ObservableCollection<Variable>(SystemDefaultSet.Variables.OrderBy(x => x.Name).ToList());
213              }
214  
215              EnvironmentVariablesHelper.SetVariable(variable);
216              PopulateAppliedVariables();
217          }
218  
219          internal void EditVariable(Variable original, Variable edited, ProfileVariablesSet variablesSet)
220          {
221              bool propagateChange = variablesSet == null /* not a profile */ || variablesSet.Id.Equals(AppliedProfile?.Id);
222              bool changed = original.Name != edited.Name || original.Values != edited.Values;
223              if (changed)
224              {
225                  var task = original.Update(edited, propagateChange, variablesSet);
226                  task.ContinueWith(x =>
227                  {
228                      _dispatcherQueue.TryEnqueue(() =>
229                      {
230                          PopulateAppliedVariables();
231                      });
232                  });
233  
234                  TelemetryInstance.Telemetry.LogEnvironmentVariablesVariableChangedEvent(original.ParentType);
235                  _ = Task.Run(SaveAsync);
236              }
237          }
238  
239          internal void AddProfile(ProfileVariablesSet profile)
240          {
241              profile.PropertyChanged += Profile_PropertyChanged;
242              if (profile.IsEnabled)
243              {
244                  UnsetAppliedProfile();
245                  SetAppliedProfile(profile);
246              }
247  
248              Profiles.Add(profile);
249  
250              _ = Task.Run(SaveAsync);
251          }
252  
253          internal void UpdateProfile(ProfileVariablesSet updatedProfile)
254          {
255              var existingProfile = Profiles.Where(x => x.Id == updatedProfile.Id).FirstOrDefault();
256              if (existingProfile != null)
257              {
258                  if (updatedProfile.IsEnabled)
259                  {
260                      // Let's unset the profile before applying the update. Even if this one is the one that's currently set.
261                      UnsetAppliedProfile();
262                  }
263  
264                  existingProfile.Name = updatedProfile.Name;
265                  existingProfile.IsEnabled = updatedProfile.IsEnabled;
266                  existingProfile.Variables = updatedProfile.Variables;
267              }
268  
269              _ = Task.Run(SaveAsync);
270          }
271  
272          private async Task SaveAsync()
273          {
274              try
275              {
276                  await _environmentVariablesService.WriteAsync(Profiles);
277              }
278              catch (Exception ex)
279              {
280                  // Show some error
281                  LoggerInstance.Logger.LogError("Failed to save to profiles.json file", ex);
282              }
283          }
284  
285          private void Profile_PropertyChanged(object sender, PropertyChangedEventArgs e)
286          {
287              var profile = sender as ProfileVariablesSet;
288  
289              if (profile != null)
290              {
291                  if (e.PropertyName == nameof(ProfileVariablesSet.IsEnabled))
292                  {
293                      if (profile.IsEnabled)
294                      {
295                          UnsetAppliedProfile();
296                          SetAppliedProfile(profile);
297  
298                          TelemetryInstance.Telemetry.LogEnvironmentVariablesProfileEnabledEvent(true);
299                      }
300                      else
301                      {
302                          UnsetAppliedProfile();
303  
304                          TelemetryInstance.Telemetry.LogEnvironmentVariablesProfileEnabledEvent(false);
305                      }
306                  }
307              }
308  
309              _ = Task.Run(SaveAsync);
310          }
311  
312          private void SetAppliedProfile(ProfileVariablesSet profile)
313          {
314              if (profile != null)
315              {
316                  if (!profile.IsApplicable())
317                  {
318                      profile.PropertyChanged -= Profile_PropertyChanged;
319                      profile.IsEnabled = false;
320                      profile.PropertyChanged += Profile_PropertyChanged;
321  
322                      EnvironmentState = EnvironmentState.ProfileNotApplicable;
323  
324                      return;
325                  }
326              }
327  
328              var task = profile.Apply();
329              task.ContinueWith((a) =>
330              {
331                  _dispatcherQueue.TryEnqueue(() =>
332                  {
333                      PopulateAppliedVariables();
334                  });
335              });
336              AppliedProfile = profile;
337          }
338  
339          private void UnsetAppliedProfile()
340          {
341              if (AppliedProfile != null)
342              {
343                  var appliedProfile = AppliedProfile;
344                  appliedProfile.PropertyChanged -= Profile_PropertyChanged;
345                  var task = AppliedProfile.UnApply();
346                  task.ContinueWith((a) =>
347                  {
348                      _dispatcherQueue.TryEnqueue(() =>
349                      {
350                          PopulateAppliedVariables();
351                      });
352                  });
353                  AppliedProfile.IsEnabled = false;
354                  AppliedProfile = null;
355                  appliedProfile.PropertyChanged += Profile_PropertyChanged;
356              }
357          }
358  
359          internal void RemoveProfile(ProfileVariablesSet profile)
360          {
361              if (profile.IsEnabled)
362              {
363                  UnsetAppliedProfile();
364              }
365  
366              Profiles.Remove(profile);
367  
368              _ = Task.Run(SaveAsync);
369          }
370  
371          internal void DeleteVariable(Variable variable, ProfileVariablesSet profile)
372          {
373              bool propagateChange = true;
374  
375              if (profile != null)
376              {
377                  // Profile variable
378                  profile.Variables.Remove(variable);
379  
380                  if (!profile.IsEnabled)
381                  {
382                      propagateChange = false;
383                  }
384  
385                  _ = Task.Run(SaveAsync);
386              }
387              else
388              {
389                  if (variable.ParentType == VariablesSetType.User)
390                  {
391                      UserDefaultSet.Variables.Remove(variable);
392                  }
393                  else if (variable.ParentType == VariablesSetType.System)
394                  {
395                      SystemDefaultSet.Variables.Remove(variable);
396                  }
397              }
398  
399              if (propagateChange)
400              {
401                  var task = Task.Run(() =>
402                  {
403                      if (profile == null)
404                      {
405                          EnvironmentVariablesHelper.UnsetVariable(variable);
406                      }
407                      else
408                      {
409                          profile.UnapplyVariable(variable);
410                      }
411                  });
412                  task.ContinueWith((a) =>
413                  {
414                      _dispatcherQueue.TryEnqueue(() =>
415                      {
416                          PopulateAppliedVariables();
417                      });
418                  });
419              }
420          }
421      }
422  }