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 }