From c4cbef37f4b888c0b79d8ea45922181aafb231f6 Mon Sep 17 00:00:00 2001 From: Soham Das Date: Wed, 3 Apr 2024 12:27:01 -0700 Subject: [PATCH 1/8] Partial auto refresh, visibility on dev drive insights card, breadcrumb back, telemetry, cleanups --- .../Strings/en-us/Resources.resw | 2 +- .../OptimizeDevDriveDialogViewModel.cs | 3 +++ .../ViewModels/DevDriveInsightsViewModel.cs | 14 ++++++++++++++ .../ViewModels/MainPageViewModel.cs | 4 ++++ .../Views/DevDriveInsightsPage.xaml | 8 +++----- .../DevHome.Customization/Views/MainPageView.xaml | 1 + 6 files changed, 26 insertions(+), 6 deletions(-) diff --git a/tools/Customization/DevHome.Customization/Strings/en-us/Resources.resw b/tools/Customization/DevHome.Customization/Strings/en-us/Resources.resw index d6a6c7a6f7..a1a9310ef8 100644 --- a/tools/Customization/DevHome.Customization/Strings/en-us/Resources.resw +++ b/tools/Customization/DevHome.Customization/Strings/en-us/Resources.resw @@ -126,7 +126,7 @@ Dev drive size free - All things, dev drives, optimizations, etc. + All things, Dev Drives, optimizations, etc. The description for the Dev Drive Insights settings card diff --git a/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/OptimizeDevDriveDialogViewModel.cs b/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/OptimizeDevDriveDialogViewModel.cs index 2d0cedc5c4..2a1c4419a1 100644 --- a/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/OptimizeDevDriveDialogViewModel.cs +++ b/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/OptimizeDevDriveDialogViewModel.cs @@ -8,6 +8,7 @@ using CommunityToolkit.Mvvm.Input; using DevHome.Common.Extensions; using DevHome.Common.Services; +using Microsoft.UI.Xaml.Controls; using Serilog; using Windows.Storage.Pickers; using WinUIEx; @@ -140,6 +141,8 @@ private void DirectoryInputConfirmed() // TODO: If chosen folder not a dev drive location, currently we no-op. Instead we should display the error. MoveDirectory(ExistingCacheLocation, directoryPath); SetEnvironmentVariable(EnvironmentVariableToBeSet, directoryPath); + + Log.Debug($"Moved cache from {ExistingCacheLocation} to {directoryPath}"); } } } diff --git a/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsightsViewModel.cs b/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsightsViewModel.cs index 5e3fa54bd4..ad52c554e3 100644 --- a/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsightsViewModel.cs +++ b/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsightsViewModel.cs @@ -12,12 +12,17 @@ using DevHome.Customization.Helpers; using DevHome.Customization.ViewModels.DevDriveInsights; using DevHome.Customization.Views; +using Microsoft.Internal.Windows.DevHome.Helpers; using Serilog; namespace DevHome.Customization.ViewModels; public partial class DevDriveInsightsViewModel : ObservableObject { + private readonly ShellSettings _shellSettings; + + public ObservableCollection Breadcrumbs { get; } + public ObservableCollection DevDriveCardCollection { get; private set; } = new(); public ObservableCollection DevDriveOptimizerCardCollection { get; private set; } = new(); @@ -54,6 +59,15 @@ public partial class DevDriveInsightsViewModel : ObservableObject public DevDriveInsightsViewModel(IDevDriveManager devDriveManager, OptimizeDevDriveDialogViewModelFactory optimizeDevDriveDialogViewModelFactory) { + _shellSettings = new ShellSettings(); + + var stringResource = new StringResource("DevHome.Customization.pri", "DevHome.Customization/Resources"); + Breadcrumbs = + [ + new(stringResource.GetLocalized("MainPage_Header"), typeof(MainPageViewModel).FullName!), + new(stringResource.GetLocalized("DevDriveInsights_Header"), typeof(DevDriveInsightsViewModel).FullName!) + ]; + _optimizeDevDriveDialogViewModelFactory = optimizeDevDriveDialogViewModelFactory; DevDriveManagerObj = devDriveManager; } diff --git a/tools/Customization/DevHome.Customization/ViewModels/MainPageViewModel.cs b/tools/Customization/DevHome.Customization/ViewModels/MainPageViewModel.cs index cff6dc052c..b54ea197af 100644 --- a/tools/Customization/DevHome.Customization/ViewModels/MainPageViewModel.cs +++ b/tools/Customization/DevHome.Customization/ViewModels/MainPageViewModel.cs @@ -3,12 +3,14 @@ using System; using System.Collections.ObjectModel; +using System.Linq; using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using DevHome.Common.Extensions; using DevHome.Common.Models; using DevHome.Common.Services; +using Microsoft.UI.Xaml; using Windows.System; namespace DevHome.Customization.ViewModels; @@ -45,4 +47,6 @@ private void NavigateToDevDriveInsightsPage() { NavigationService.NavigateTo(typeof(DevDriveInsightsViewModel).FullName!); } + + public bool AnyDevDrivesPresent => Application.Current.GetService().GetAllDevDrivesThatExistOnSystem().Any(); } diff --git a/tools/Customization/DevHome.Customization/Views/DevDriveInsightsPage.xaml b/tools/Customization/DevHome.Customization/Views/DevDriveInsightsPage.xaml index abce444f88..60b13945da 100644 --- a/tools/Customization/DevHome.Customization/Views/DevDriveInsightsPage.xaml +++ b/tools/Customization/DevHome.Customization/Views/DevDriveInsightsPage.xaml @@ -2,12 +2,10 @@ x:Class="DevHome.Customization.Views.DevDriveInsightsPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:behaviors="using:DevHome.Common.Behaviors" - xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:behaviors="using:DevHome.Common.Behaviors" xmlns:views="using:DevHome.Customization.Views" - behaviors:NavigationViewHeaderBehavior.HeaderMode="Always" - mc:Ignorable="d"> + behaviors:NavigationViewHeaderBehavior.HeaderTemplate="{StaticResource BreadcrumbBarDataTemplate}" + behaviors:NavigationViewHeaderBehavior.HeaderContext="{x:Bind ViewModel}"> diff --git a/tools/Customization/DevHome.Customization/Views/MainPageView.xaml b/tools/Customization/DevHome.Customization/Views/MainPageView.xaml index 64ad93ec49..c7b8080aae 100644 --- a/tools/Customization/DevHome.Customization/Views/MainPageView.xaml +++ b/tools/Customization/DevHome.Customization/Views/MainPageView.xaml @@ -22,6 +22,7 @@ AutomationProperties.AccessibilityView="Control" AutomationProperties.AutomationId="NavigateDevDriveInsightsCardButton" Command="{x:Bind ViewModel.NavigateToDevDriveInsightsPageCommand}" + Visibility="{x:Bind ViewModel.AnyDevDrivesPresent}" IsClickEnabled="True" > From 0967d46ef987cbefafcd313d417c65e3e952a65a Mon Sep 17 00:00:00 2001 From: Soham Das Date: Mon, 8 Apr 2024 10:26:24 -0700 Subject: [PATCH 2/8] More caches optimized --- .../Extensions/ServiceExtensions.cs | 2 +- .../Strings/en-us/Resources.resw | 4 -- .../DevDriveOptimizerCardViewModel.cs | 2 +- .../OptimizeDevDriveDialogViewModel.cs | 4 +- .../ViewModels/DevDriveInsightsViewModel.cs | 67 +++++++++++++++++-- .../Views/OptimizeDevDriveDialog.xaml.cs | 2 +- 6 files changed, 65 insertions(+), 16 deletions(-) diff --git a/tools/Customization/DevHome.Customization/Extensions/ServiceExtensions.cs b/tools/Customization/DevHome.Customization/Extensions/ServiceExtensions.cs index a309f9182e..4e7fa3ff07 100644 --- a/tools/Customization/DevHome.Customization/Extensions/ServiceExtensions.cs +++ b/tools/Customization/DevHome.Customization/Extensions/ServiceExtensions.cs @@ -19,7 +19,7 @@ public static IServiceCollection AddWindowsCustomization(this IServiceCollection services.AddSingleton(); services.AddTransient(); - services.AddSingleton(sp => (cacheLocation, environmentVariable) => ActivatorUtilities.CreateInstance(sp, cacheLocation, environmentVariable)); + services.AddSingleton(sp => (cacheLocation, environmentVariable, exampleDevDriveLocation) => ActivatorUtilities.CreateInstance(sp, cacheLocation, environmentVariable, exampleDevDriveLocation)); services.AddSingleton(); services.AddTransient(); diff --git a/tools/Customization/DevHome.Customization/Strings/en-us/Resources.resw b/tools/Customization/DevHome.Customization/Strings/en-us/Resources.resw index a1a9310ef8..7453471253 100644 --- a/tools/Customization/DevHome.Customization/Strings/en-us/Resources.resw +++ b/tools/Customization/DevHome.Customization/Strings/en-us/Resources.resw @@ -161,10 +161,6 @@ Enable end task in taskbar by right click The description for the end task on task bar settings card - - Example: E:\packages\pip - Example dev drive location - End Task The header for the end task on task bar settings card diff --git a/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/DevDriveOptimizerCardViewModel.cs b/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/DevDriveOptimizerCardViewModel.cs index aa64ba3f0b..e144f1ef35 100644 --- a/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/DevDriveOptimizerCardViewModel.cs +++ b/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/DevDriveOptimizerCardViewModel.cs @@ -41,7 +41,7 @@ private async Task OptimizeDevDriveAsync(object sender) var settingsCard = sender as Button; if (settingsCard != null) { - var optimizeDevDriveViewModel = OptimizeDevDriveDialogViewModelFactory(ExistingCacheLocation, EnvironmentVariableToBeSet); + var optimizeDevDriveViewModel = OptimizeDevDriveDialogViewModelFactory(ExistingCacheLocation, EnvironmentVariableToBeSet, ExampleLocationOnDevDrive); var optimizeDevDriveDialog = new OptimizeDevDriveDialog(optimizeDevDriveViewModel); optimizeDevDriveDialog.XamlRoot = settingsCard.XamlRoot; optimizeDevDriveDialog.RequestedTheme = settingsCard.ActualTheme; diff --git a/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/OptimizeDevDriveDialogViewModel.cs b/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/OptimizeDevDriveDialogViewModel.cs index 2a1c4419a1..3490acdfff 100644 --- a/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/OptimizeDevDriveDialogViewModel.cs +++ b/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/OptimizeDevDriveDialogViewModel.cs @@ -41,11 +41,11 @@ public partial class OptimizeDevDriveDialogViewModel : ObservableObject [ObservableProperty] private string _directoryPathTextBox; - public OptimizeDevDriveDialogViewModel(string existingCacheLocation, string environmentVariableToBeSet) + public OptimizeDevDriveDialogViewModel(string existingCacheLocation, string environmentVariableToBeSet, string exampleDevDriveLocation) { DirectoryPathTextBox = string.Empty; var stringResource = new StringResource("DevHome.Customization.pri", "DevHome.Customization/Resources"); - ExampleDevDriveLocation = stringResource.GetLocalized("ExampleDevDriveLocation"); + ExampleDevDriveLocation = exampleDevDriveLocation; ChooseDirectoryPromptText = stringResource.GetLocalized("ChooseDirectoryPromptText"); MakeChangesText = stringResource.GetLocalized("MakeChangesText"); ExistingCacheLocation = existingCacheLocation; diff --git a/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsightsViewModel.cs b/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsightsViewModel.cs index ad52c554e3..045a6c9797 100644 --- a/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsightsViewModel.cs +++ b/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsightsViewModel.cs @@ -53,10 +53,20 @@ public partial class DevDriveInsightsViewModel : ObservableObject private IEnumerable ExistingDevDrives { get; set; } = Enumerable.Empty(); + private static readonly string _appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + private static readonly string _localAppDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); private static readonly string _userProfilePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + private const string ExampleDevDriveDirStr = "D:"; + + private const string PackagesStr = "packages"; + + private const string CacheStr = "cache"; + + private const string ArchivesStr = "archives"; + public DevDriveInsightsViewModel(IDevDriveManager devDriveManager, OptimizeDevDriveDialogViewModelFactory optimizeDevDriveDialogViewModelFactory) { _shellSettings = new ShellSettings(); @@ -251,17 +261,60 @@ public void UpdateListViewModelList() EnvironmentVariable = "PIP_CACHE_DIR", CacheDirectory = new List { - Path.Join(_localAppDataPath, "pip", "cache"), - Path.Join(_localAppDataPath, "packages", "PythonSoftwareFoundation.Python"), + Path.Join(_localAppDataPath, "pip", CacheStr), + Path.Join(_localAppDataPath, PackagesStr, "PythonSoftwareFoundation.Python"), }, - ExampleDirectory = Path.Join("D:", "packages", "pip", "cache"), + ExampleDirectory = Path.Join(ExampleDevDriveDirStr, PackagesStr, "pip", CacheStr), }, new DevDriveCacheData { CacheName = "NuGet cache (dotnet)", EnvironmentVariable = "NUGET_PACKAGES", - CacheDirectory = new List { Path.Join(_userProfilePath, ".nuget", "packages") }, - ExampleDirectory = Path.Join("D:", "packages", "NuGet", "Cache"), + CacheDirectory = new List { Path.Join(_userProfilePath, ".nuget", PackagesStr) }, + ExampleDirectory = Path.Join(ExampleDevDriveDirStr, PackagesStr, "NuGet", CacheStr), + }, + new DevDriveCacheData + { + CacheName = "Npm cache (NodeJS)", + EnvironmentVariable = "NPM_CONFIG_CACHE", + CacheDirectory = new List + { + Path.Join(_appDataPath, "npm-cache"), + Path.Join(_localAppDataPath, "npm-cache"), + }, + ExampleDirectory = Path.Join(ExampleDevDriveDirStr, PackagesStr, "npm"), + }, + new DevDriveCacheData + { + CacheName = "Vcpkg cache", + EnvironmentVariable = "VCPKG_DEFAULT_BINARY_CACHE", + CacheDirectory = new List + { + Path.Join(_appDataPath, "vcpkg", ArchivesStr), + Path.Join(_localAppDataPath, "vcpkg", ArchivesStr), + }, + ExampleDirectory = Path.Join(ExampleDevDriveDirStr, PackagesStr, "vcpkg"), + }, + new DevDriveCacheData + { + CacheName = "Cargo cache (Rust)", + EnvironmentVariable = "CARGO_HOME", + CacheDirectory = new List { Path.Join(_userProfilePath, ".cargo") }, + ExampleDirectory = Path.Join(ExampleDevDriveDirStr, PackagesStr, "cargo"), + }, + new DevDriveCacheData + { + CacheName = "Maven cache (Java)", + EnvironmentVariable = "MAVEN_OPTS", + CacheDirectory = new List { Path.Join(_userProfilePath, ".m2") }, + ExampleDirectory = Path.Join(ExampleDevDriveDirStr, PackagesStr, "m2"), + }, + new DevDriveCacheData + { + CacheName = "Gradle cache (Java)", + EnvironmentVariable = "GRADLE_USER_HOME", + CacheDirectory = new List { Path.Join(_userProfilePath, ".gradle") }, + ExampleDirectory = Path.Join(ExampleDevDriveDirStr, PackagesStr, "gradle"), } ]; @@ -275,13 +328,13 @@ public void UpdateListViewModelList() } else { - var subDirectories = Directory.GetDirectories(_localAppDataPath + "\\Packages", "*", SearchOption.TopDirectoryOnly); + var subDirectories = Directory.GetDirectories(Path.Join(_localAppDataPath, PackagesStr), "*", SearchOption.TopDirectoryOnly); var matchingSubdirectory = subDirectories.FirstOrDefault(subdir => subdir.StartsWith(cacheDirectory, StringComparison.OrdinalIgnoreCase)); if (Directory.Exists(matchingSubdirectory)) { if (matchingSubdirectory.Contains("PythonSoftwareFoundation")) { - return Path.Join(matchingSubdirectory, "LocalCache", "Local", "pip", "cache"); + return Path.Join(matchingSubdirectory, "LocalCache", "Local", "pip", CacheStr); } return matchingSubdirectory; diff --git a/tools/Customization/DevHome.Customization/Views/OptimizeDevDriveDialog.xaml.cs b/tools/Customization/DevHome.Customization/Views/OptimizeDevDriveDialog.xaml.cs index 8191dc757f..467d442a62 100644 --- a/tools/Customization/DevHome.Customization/Views/OptimizeDevDriveDialog.xaml.cs +++ b/tools/Customization/DevHome.Customization/Views/OptimizeDevDriveDialog.xaml.cs @@ -6,7 +6,7 @@ namespace DevHome.Customization.Views; -public delegate OptimizeDevDriveDialogViewModel OptimizeDevDriveDialogViewModelFactory(string existingCacheLocation, string environmentVariableToBeSet); +public delegate OptimizeDevDriveDialogViewModel OptimizeDevDriveDialogViewModelFactory(string existingCacheLocation, string environmentVariableToBeSet, string exampleDevDriveLocation); public sealed partial class OptimizeDevDriveDialog : ContentDialog { From c2431312bdc67400743d3ce00d81499d5f8a6513 Mon Sep 17 00:00:00 2001 From: Soham Das Date: Mon, 8 Apr 2024 14:23:04 -0700 Subject: [PATCH 3/8] Improvements --- .../Extensions/ServiceExtensions.cs | 2 +- .../Strings/en-us/Resources.resw | 4 ++ .../DevDriveOptimizerCardViewModel.cs | 18 +++++++- .../OptimizeDevDriveDialogViewModel.cs | 41 ++++++++++++++++--- .../ViewModels/DevDriveInsightsViewModel.cs | 9 +++- .../Views/OptimizeDevDriveDialog.xaml.cs | 7 +++- 6 files changed, 70 insertions(+), 11 deletions(-) diff --git a/tools/Customization/DevHome.Customization/Extensions/ServiceExtensions.cs b/tools/Customization/DevHome.Customization/Extensions/ServiceExtensions.cs index 4e7fa3ff07..b06769e22b 100644 --- a/tools/Customization/DevHome.Customization/Extensions/ServiceExtensions.cs +++ b/tools/Customization/DevHome.Customization/Extensions/ServiceExtensions.cs @@ -19,7 +19,7 @@ public static IServiceCollection AddWindowsCustomization(this IServiceCollection services.AddSingleton(); services.AddTransient(); - services.AddSingleton(sp => (cacheLocation, environmentVariable, exampleDevDriveLocation) => ActivatorUtilities.CreateInstance(sp, cacheLocation, environmentVariable, exampleDevDriveLocation)); + services.AddSingleton(sp => (cacheLocation, environmentVariable, exampleDevDriveLocation, existingDevDriveLetters) => ActivatorUtilities.CreateInstance(sp, cacheLocation, environmentVariable, exampleDevDriveLocation, existingDevDriveLetters)); services.AddSingleton(); services.AddTransient(); diff --git a/tools/Customization/DevHome.Customization/Strings/en-us/Resources.resw b/tools/Customization/DevHome.Customization/Strings/en-us/Resources.resw index 7453471253..51c629d08a 100644 --- a/tools/Customization/DevHome.Customization/Strings/en-us/Resources.resw +++ b/tools/Customization/DevHome.Customization/Strings/en-us/Resources.resw @@ -161,6 +161,10 @@ Enable end task in taskbar by right click The description for the end task on task bar settings card + + Example: + Example string + End Task The header for the end task on task bar settings card diff --git a/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/DevDriveOptimizerCardViewModel.cs b/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/DevDriveOptimizerCardViewModel.cs index e144f1ef35..e0175b81de 100644 --- a/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/DevDriveOptimizerCardViewModel.cs +++ b/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/DevDriveOptimizerCardViewModel.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; @@ -18,6 +19,8 @@ public partial class DevDriveOptimizerCardViewModel : ObservableObject { public OptimizeDevDriveDialogViewModelFactory OptimizeDevDriveDialogViewModelFactory { get; set; } + public List ExistingDevDriveLetters { get; set; } + public string CacheToBeMoved { get; set; } public string DevDriveOptimizationSuggestion { get; set; } @@ -41,7 +44,11 @@ private async Task OptimizeDevDriveAsync(object sender) var settingsCard = sender as Button; if (settingsCard != null) { - var optimizeDevDriveViewModel = OptimizeDevDriveDialogViewModelFactory(ExistingCacheLocation, EnvironmentVariableToBeSet, ExampleLocationOnDevDrive); + var optimizeDevDriveViewModel = OptimizeDevDriveDialogViewModelFactory( + ExistingCacheLocation, + EnvironmentVariableToBeSet, + ExampleLocationOnDevDrive, + ExistingDevDriveLetters); var optimizeDevDriveDialog = new OptimizeDevDriveDialog(optimizeDevDriveViewModel); optimizeDevDriveDialog.XamlRoot = settingsCard.XamlRoot; optimizeDevDriveDialog.RequestedTheme = settingsCard.ActualTheme; @@ -49,9 +56,16 @@ private async Task OptimizeDevDriveAsync(object sender) } } - public DevDriveOptimizerCardViewModel(OptimizeDevDriveDialogViewModelFactory optimizeDevDriveDialogViewModelFactory, string cacheToBeMoved, string existingCacheLocation, string exampleLocationOnDevDrive, string environmentVariableToBeSet) + public DevDriveOptimizerCardViewModel( + OptimizeDevDriveDialogViewModelFactory optimizeDevDriveDialogViewModelFactory, + string cacheToBeMoved, + string existingCacheLocation, + string exampleLocationOnDevDrive, + string environmentVariableToBeSet, + List existingDevDriveLetters) { OptimizeDevDriveDialogViewModelFactory = optimizeDevDriveDialogViewModelFactory; + ExistingDevDriveLetters = existingDevDriveLetters; CacheToBeMoved = cacheToBeMoved; ExistingCacheLocation = existingCacheLocation; ExampleLocationOnDevDrive = exampleLocationOnDevDrive; diff --git a/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/OptimizeDevDriveDialogViewModel.cs b/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/OptimizeDevDriveDialogViewModel.cs index 3490acdfff..8d08be6540 100644 --- a/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/OptimizeDevDriveDialogViewModel.cs +++ b/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/OptimizeDevDriveDialogViewModel.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; @@ -20,6 +21,9 @@ namespace DevHome.Customization.ViewModels.DevDriveInsights; /// public partial class OptimizeDevDriveDialogViewModel : ObservableObject { + [ObservableProperty] + private List _existingDevDriveLetters; + [ObservableProperty] private string _exampleDevDriveLocation; @@ -41,11 +45,16 @@ public partial class OptimizeDevDriveDialogViewModel : ObservableObject [ObservableProperty] private string _directoryPathTextBox; - public OptimizeDevDriveDialogViewModel(string existingCacheLocation, string environmentVariableToBeSet, string exampleDevDriveLocation) + public OptimizeDevDriveDialogViewModel( + string existingCacheLocation, + string environmentVariableToBeSet, + string exampleDevDriveLocation, + List existingDevDriveLetters) { DirectoryPathTextBox = string.Empty; var stringResource = new StringResource("DevHome.Customization.pri", "DevHome.Customization/Resources"); - ExampleDevDriveLocation = exampleDevDriveLocation; + ExistingDevDriveLetters = existingDevDriveLetters; + ExampleDevDriveLocation = stringResource.GetLocalized("ExampleText") + exampleDevDriveLocation; ChooseDirectoryPromptText = stringResource.GetLocalized("ChooseDirectoryPromptText"); MakeChangesText = stringResource.GetLocalized("MakeChangesText"); ExistingCacheLocation = existingCacheLocation; @@ -130,6 +139,19 @@ private void SetEnvironmentVariable(string variableName, string value) } } + private bool ChosenDirectoryInDevDrive(string directoryPath) + { + foreach (var devDriveLetter in ExistingDevDriveLetters) + { + if (directoryPath.StartsWith(devDriveLetter + ":", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + } + [RelayCommand] private void DirectoryInputConfirmed() { @@ -138,11 +160,18 @@ private void DirectoryInputConfirmed() if (!string.IsNullOrEmpty(directoryPath)) { // Handle the selected folder - // TODO: If chosen folder not a dev drive location, currently we no-op. Instead we should display the error. - MoveDirectory(ExistingCacheLocation, directoryPath); - SetEnvironmentVariable(EnvironmentVariableToBeSet, directoryPath); + // TODO: If chosen folder not a dev drive location, currently we no-op and log the error. Instead we should display the error. + if (ChosenDirectoryInDevDrive(directoryPath)) + { + MoveDirectory(ExistingCacheLocation, directoryPath); + SetEnvironmentVariable(EnvironmentVariableToBeSet, directoryPath); - Log.Debug($"Moved cache from {ExistingCacheLocation} to {directoryPath}"); + Log.Debug($"Moved cache from {ExistingCacheLocation} to {directoryPath}"); + } + else + { + Log.Error($"Chosen directory {directoryPath} not on a dev drive."); + } } } } diff --git a/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsightsViewModel.cs b/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsightsViewModel.cs index 045a6c9797..0ef2ac49e2 100644 --- a/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsightsViewModel.cs +++ b/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsightsViewModel.cs @@ -374,12 +374,19 @@ public void UpdateOptimizerListViewModelList() continue; } + List existingDevDriveLetters = new List(); + foreach (var existingDevDrive in ExistingDevDrives) + { + existingDevDriveLetters.Add(existingDevDrive.DriveLetter.ToString()); + } + var card = new DevDriveOptimizerCardViewModel( _optimizeDevDriveDialogViewModelFactory, cache.CacheName!, existingCacheLocation, cache.ExampleDirectory!, // example location on dev drive to move cache to - cache.EnvironmentVariable!); // environmentVariableToBeSet + cache.EnvironmentVariable!, // environmentVariableToBeSet + existingDevDriveLetters); DevDriveOptimizerCardCollection.Add(card); } diff --git a/tools/Customization/DevHome.Customization/Views/OptimizeDevDriveDialog.xaml.cs b/tools/Customization/DevHome.Customization/Views/OptimizeDevDriveDialog.xaml.cs index 467d442a62..b6cde57e4b 100644 --- a/tools/Customization/DevHome.Customization/Views/OptimizeDevDriveDialog.xaml.cs +++ b/tools/Customization/DevHome.Customization/Views/OptimizeDevDriveDialog.xaml.cs @@ -1,12 +1,17 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Collections.Generic; using DevHome.Customization.ViewModels.DevDriveInsights; using Microsoft.UI.Xaml.Controls; namespace DevHome.Customization.Views; -public delegate OptimizeDevDriveDialogViewModel OptimizeDevDriveDialogViewModelFactory(string existingCacheLocation, string environmentVariableToBeSet, string exampleDevDriveLocation); +public delegate OptimizeDevDriveDialogViewModel OptimizeDevDriveDialogViewModelFactory( + string existingCacheLocation, + string environmentVariableToBeSet, + string exampleDevDriveLocation, + List existingDevDriveLetters); public sealed partial class OptimizeDevDriveDialog : ContentDialog { From 14100eef644afa22dc7a8f1d2e702d3bc5da2ecb Mon Sep 17 00:00:00 2001 From: Soham Das Date: Mon, 8 Apr 2024 15:38:07 -0700 Subject: [PATCH 4/8] Added telemetry for Data team --- .../OptimizeDevDriveDialogViewModel.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/OptimizeDevDriveDialogViewModel.cs b/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/OptimizeDevDriveDialogViewModel.cs index 8d08be6540..509e3c776f 100644 --- a/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/OptimizeDevDriveDialogViewModel.cs +++ b/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/OptimizeDevDriveDialogViewModel.cs @@ -9,8 +9,11 @@ using CommunityToolkit.Mvvm.Input; using DevHome.Common.Extensions; using DevHome.Common.Services; +using DevHome.Common.TelemetryEvents; +using DevHome.Telemetry; using Microsoft.UI.Xaml.Controls; using Serilog; +using Windows.Media.Protection; using Windows.Storage.Pickers; using WinUIEx; @@ -88,7 +91,7 @@ private async Task BrowseButtonClick(object sender) } } - private void MoveDirectory(string sourceDirectory, string targetDirectory) + private int MoveDirectory(string sourceDirectory, string targetDirectory) { try { @@ -120,10 +123,13 @@ private void MoveDirectory(string sourceDirectory, string targetDirectory) // Delete the source directory Directory.Delete(sourceDirectory, true); + return 0; } catch (Exception ex) { Log.Error($"Error in MoveDirectory. Error: {ex}"); + TelemetryFactory.Get().LogError("DevDriveInsights_PackageCacheMove_Error", LogLevel.Critical, new ExceptionEvent(ex.HResult, sourceDirectory)); + return ex.HResult; } } @@ -163,10 +169,12 @@ private void DirectoryInputConfirmed() // TODO: If chosen folder not a dev drive location, currently we no-op and log the error. Instead we should display the error. if (ChosenDirectoryInDevDrive(directoryPath)) { - MoveDirectory(ExistingCacheLocation, directoryPath); - SetEnvironmentVariable(EnvironmentVariableToBeSet, directoryPath); - - Log.Debug($"Moved cache from {ExistingCacheLocation} to {directoryPath}"); + if (MoveDirectory(ExistingCacheLocation, directoryPath) == 0) + { + SetEnvironmentVariable(EnvironmentVariableToBeSet, directoryPath); + Log.Debug($"Moved cache from {ExistingCacheLocation} to {directoryPath}"); + TelemetryFactory.Get().Log("DevDriveInsights_PackageCacheMoved_Event", LogLevel.Critical, new ExceptionEvent(0, ExistingCacheLocation)); + } } else { From caeaad9d7655eab333f6f495a2c366fed0a68d1a Mon Sep 17 00:00:00 2001 From: Soham Das Date: Tue, 9 Apr 2024 12:24:04 -0700 Subject: [PATCH 5/8] Addressed PR comments --- .../Helpers/DevDriveCacheData.cs | 2 +- .../Strings/en-us/Resources.resw | 2 +- .../OptimizeDevDriveDialogViewModel.cs | 4 ++-- .../ViewModels/DevDriveInsightsViewModel.cs | 19 +++++++++---------- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/tools/Customization/DevHome.Customization/Helpers/DevDriveCacheData.cs b/tools/Customization/DevHome.Customization/Helpers/DevDriveCacheData.cs index 8fb1762a18..8b9223c3cc 100644 --- a/tools/Customization/DevHome.Customization/Helpers/DevDriveCacheData.cs +++ b/tools/Customization/DevHome.Customization/Helpers/DevDriveCacheData.cs @@ -17,5 +17,5 @@ public partial class DevDriveCacheData public List? CacheDirectory { get; set; } - public string? ExampleDirectory { get; set; } + public string? ExampleSubDirectory { get; set; } } diff --git a/tools/Customization/DevHome.Customization/Strings/en-us/Resources.resw b/tools/Customization/DevHome.Customization/Strings/en-us/Resources.resw index 51c629d08a..3914c8eb00 100644 --- a/tools/Customization/DevHome.Customization/Strings/en-us/Resources.resw +++ b/tools/Customization/DevHome.Customization/Strings/en-us/Resources.resw @@ -163,7 +163,7 @@ Example: - Example string + Example string, will be followed by a sample location to move the cache to a dev drive location End Task diff --git a/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/OptimizeDevDriveDialogViewModel.cs b/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/OptimizeDevDriveDialogViewModel.cs index 509e3c776f..a87f2804bb 100644 --- a/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/OptimizeDevDriveDialogViewModel.cs +++ b/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/OptimizeDevDriveDialogViewModel.cs @@ -128,7 +128,7 @@ private int MoveDirectory(string sourceDirectory, string targetDirectory) catch (Exception ex) { Log.Error($"Error in MoveDirectory. Error: {ex}"); - TelemetryFactory.Get().LogError("DevDriveInsights_PackageCacheMove_Error", LogLevel.Critical, new ExceptionEvent(ex.HResult, sourceDirectory)); + TelemetryFactory.Get().LogError("DevDriveInsights_PackageCacheMoveDirectory_Error", LogLevel.Critical, new ExceptionEvent(ex.HResult, sourceDirectory)); return ex.HResult; } } @@ -173,7 +173,7 @@ private void DirectoryInputConfirmed() { SetEnvironmentVariable(EnvironmentVariableToBeSet, directoryPath); Log.Debug($"Moved cache from {ExistingCacheLocation} to {directoryPath}"); - TelemetryFactory.Get().Log("DevDriveInsights_PackageCacheMoved_Event", LogLevel.Critical, new ExceptionEvent(0, ExistingCacheLocation)); + TelemetryFactory.Get().Log("DevDriveInsights_PackageCacheMovedSuccessfully_Event", LogLevel.Critical, new ExceptionEvent(0, ExistingCacheLocation)); } } else diff --git a/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsightsViewModel.cs b/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsightsViewModel.cs index 0ef2ac49e2..8f22618656 100644 --- a/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsightsViewModel.cs +++ b/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsightsViewModel.cs @@ -59,8 +59,6 @@ public partial class DevDriveInsightsViewModel : ObservableObject private static readonly string _userProfilePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); - private const string ExampleDevDriveDirStr = "D:"; - private const string PackagesStr = "packages"; private const string CacheStr = "cache"; @@ -264,14 +262,14 @@ public void UpdateListViewModelList() Path.Join(_localAppDataPath, "pip", CacheStr), Path.Join(_localAppDataPath, PackagesStr, "PythonSoftwareFoundation.Python"), }, - ExampleDirectory = Path.Join(ExampleDevDriveDirStr, PackagesStr, "pip", CacheStr), + ExampleSubDirectory = Path.Join(PackagesStr, "pip", CacheStr), }, new DevDriveCacheData { CacheName = "NuGet cache (dotnet)", EnvironmentVariable = "NUGET_PACKAGES", CacheDirectory = new List { Path.Join(_userProfilePath, ".nuget", PackagesStr) }, - ExampleDirectory = Path.Join(ExampleDevDriveDirStr, PackagesStr, "NuGet", CacheStr), + ExampleSubDirectory = Path.Join(PackagesStr, "NuGet", CacheStr), }, new DevDriveCacheData { @@ -282,7 +280,7 @@ public void UpdateListViewModelList() Path.Join(_appDataPath, "npm-cache"), Path.Join(_localAppDataPath, "npm-cache"), }, - ExampleDirectory = Path.Join(ExampleDevDriveDirStr, PackagesStr, "npm"), + ExampleSubDirectory = Path.Join(PackagesStr, "npm"), }, new DevDriveCacheData { @@ -293,28 +291,28 @@ public void UpdateListViewModelList() Path.Join(_appDataPath, "vcpkg", ArchivesStr), Path.Join(_localAppDataPath, "vcpkg", ArchivesStr), }, - ExampleDirectory = Path.Join(ExampleDevDriveDirStr, PackagesStr, "vcpkg"), + ExampleSubDirectory = Path.Join(PackagesStr, "vcpkg"), }, new DevDriveCacheData { CacheName = "Cargo cache (Rust)", EnvironmentVariable = "CARGO_HOME", CacheDirectory = new List { Path.Join(_userProfilePath, ".cargo") }, - ExampleDirectory = Path.Join(ExampleDevDriveDirStr, PackagesStr, "cargo"), + ExampleSubDirectory = Path.Join(PackagesStr, "cargo"), }, new DevDriveCacheData { CacheName = "Maven cache (Java)", EnvironmentVariable = "MAVEN_OPTS", CacheDirectory = new List { Path.Join(_userProfilePath, ".m2") }, - ExampleDirectory = Path.Join(ExampleDevDriveDirStr, PackagesStr, "m2"), + ExampleSubDirectory = Path.Join(PackagesStr, "m2"), }, new DevDriveCacheData { CacheName = "Gradle cache (Java)", EnvironmentVariable = "GRADLE_USER_HOME", CacheDirectory = new List { Path.Join(_userProfilePath, ".gradle") }, - ExampleDirectory = Path.Join(ExampleDevDriveDirStr, PackagesStr, "gradle"), + ExampleSubDirectory = Path.Join(PackagesStr, "gradle"), } ]; @@ -380,11 +378,12 @@ public void UpdateOptimizerListViewModelList() existingDevDriveLetters.Add(existingDevDrive.DriveLetter.ToString()); } + var exampleDirectory = Path.Join(existingDevDriveLetters[0] + ":", cache.ExampleSubDirectory); var card = new DevDriveOptimizerCardViewModel( _optimizeDevDriveDialogViewModelFactory, cache.CacheName!, existingCacheLocation, - cache.ExampleDirectory!, // example location on dev drive to move cache to + exampleDirectory!, // example location on dev drive to move cache to cache.EnvironmentVariable!, // environmentVariableToBeSet existingDevDriveLetters); DevDriveOptimizerCardCollection.Add(card); From 8db7405e835324e25bb3be7190325d929dd8c232 Mon Sep 17 00:00:00 2001 From: Soham Das Date: Tue, 9 Apr 2024 14:03:24 -0700 Subject: [PATCH 6/8] Addressed PR comments --- .../OptimizeDevDriveDialogViewModel.cs | 8 ++--- .../Views/DevDriveInsightsPage.xaml | 33 ++++++++----------- 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/OptimizeDevDriveDialogViewModel.cs b/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/OptimizeDevDriveDialogViewModel.cs index a87f2804bb..57f084fd20 100644 --- a/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/OptimizeDevDriveDialogViewModel.cs +++ b/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/OptimizeDevDriveDialogViewModel.cs @@ -91,7 +91,7 @@ private async Task BrowseButtonClick(object sender) } } - private int MoveDirectory(string sourceDirectory, string targetDirectory) + private bool MoveDirectory(string sourceDirectory, string targetDirectory) { try { @@ -123,13 +123,13 @@ private int MoveDirectory(string sourceDirectory, string targetDirectory) // Delete the source directory Directory.Delete(sourceDirectory, true); - return 0; + return true; } catch (Exception ex) { Log.Error($"Error in MoveDirectory. Error: {ex}"); TelemetryFactory.Get().LogError("DevDriveInsights_PackageCacheMoveDirectory_Error", LogLevel.Critical, new ExceptionEvent(ex.HResult, sourceDirectory)); - return ex.HResult; + return false; } } @@ -169,7 +169,7 @@ private void DirectoryInputConfirmed() // TODO: If chosen folder not a dev drive location, currently we no-op and log the error. Instead we should display the error. if (ChosenDirectoryInDevDrive(directoryPath)) { - if (MoveDirectory(ExistingCacheLocation, directoryPath) == 0) + if (MoveDirectory(ExistingCacheLocation, directoryPath)) { SetEnvironmentVariable(EnvironmentVariableToBeSet, directoryPath); Log.Debug($"Moved cache from {ExistingCacheLocation} to {directoryPath}"); diff --git a/tools/Customization/DevHome.Customization/Views/DevDriveInsightsPage.xaml b/tools/Customization/DevHome.Customization/Views/DevDriveInsightsPage.xaml index 60b13945da..51b0f534d1 100644 --- a/tools/Customization/DevHome.Customization/Views/DevDriveInsightsPage.xaml +++ b/tools/Customization/DevHome.Customization/Views/DevDriveInsightsPage.xaml @@ -1,20 +1,15 @@ - - - - - - - - - - - - - + xmlns:views="using:DevHome.Customization.Views" + behaviors:NavigationViewHeaderBehavior.HeaderTemplate="{StaticResource BreadcrumbBarDataTemplate}" + behaviors:NavigationViewHeaderBehavior.HeaderContext="{x:Bind ViewModel}"> + + + + + + + From 25b19ec7856dda327a85245f3b4188caa87c12c5 Mon Sep 17 00:00:00 2001 From: Soham Das Date: Wed, 10 Apr 2024 10:01:24 -0700 Subject: [PATCH 7/8] Fix scrollbar --- .../DevHome.Customization/Views/DevDriveInsightsPage.xaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/Customization/DevHome.Customization/Views/DevDriveInsightsPage.xaml b/tools/Customization/DevHome.Customization/Views/DevDriveInsightsPage.xaml index 51b0f534d1..e9ad085133 100644 --- a/tools/Customization/DevHome.Customization/Views/DevDriveInsightsPage.xaml +++ b/tools/Customization/DevHome.Customization/Views/DevDriveInsightsPage.xaml @@ -7,9 +7,9 @@ behaviors:NavigationViewHeaderBehavior.HeaderTemplate="{StaticResource BreadcrumbBarDataTemplate}" behaviors:NavigationViewHeaderBehavior.HeaderContext="{x:Bind ViewModel}"> - - + + - - + + From b9089237568c8ed2267835e7b3ba76c1f0aad4f5 Mon Sep 17 00:00:00 2001 From: Soham Das Date: Wed, 10 Apr 2024 12:28:43 -0700 Subject: [PATCH 8/8] Addressed PR comments, removed private info from logs --- .../OptimizeDevDriveDialogViewModel.cs | 22 ++++++++++++++++--- .../ViewModels/DevDriveInsightsViewModel.cs | 6 +---- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/OptimizeDevDriveDialogViewModel.cs b/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/OptimizeDevDriveDialogViewModel.cs index 57f084fd20..62e1c96e46 100644 --- a/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/OptimizeDevDriveDialogViewModel.cs +++ b/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsights/OptimizeDevDriveDialogViewModel.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Data.SqlTypes; using System.IO; using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; @@ -16,6 +17,7 @@ using Windows.Media.Protection; using Windows.Storage.Pickers; using WinUIEx; +using static Microsoft.Extensions.Logging.EventSource.LoggingEventSource; namespace DevHome.Customization.ViewModels.DevDriveInsights; @@ -91,6 +93,19 @@ private async Task BrowseButtonClick(object sender) } } + private string RemovePrivacyInfo(string input) + { + var output = input; + var userProfilePath = Environment.ExpandEnvironmentVariables("%userprofile%"); + if (input.StartsWith(userProfilePath, StringComparison.OrdinalIgnoreCase)) + { + var index = input.LastIndexOf(userProfilePath, StringComparison.OrdinalIgnoreCase) + userProfilePath.Length; + output = Path.Join("%userprofile%", input.Substring(index)); + } + + return output; + } + private bool MoveDirectory(string sourceDirectory, string targetDirectory) { try @@ -128,7 +143,7 @@ private bool MoveDirectory(string sourceDirectory, string targetDirectory) catch (Exception ex) { Log.Error($"Error in MoveDirectory. Error: {ex}"); - TelemetryFactory.Get().LogError("DevDriveInsights_PackageCacheMoveDirectory_Error", LogLevel.Critical, new ExceptionEvent(ex.HResult, sourceDirectory)); + TelemetryFactory.Get().LogError("DevDriveInsights_PackageCacheMoveDirectory_Error", LogLevel.Critical, new ExceptionEvent(ex.HResult, RemovePrivacyInfo(sourceDirectory))); return false; } } @@ -172,8 +187,9 @@ private void DirectoryInputConfirmed() if (MoveDirectory(ExistingCacheLocation, directoryPath)) { SetEnvironmentVariable(EnvironmentVariableToBeSet, directoryPath); - Log.Debug($"Moved cache from {ExistingCacheLocation} to {directoryPath}"); - TelemetryFactory.Get().Log("DevDriveInsights_PackageCacheMovedSuccessfully_Event", LogLevel.Critical, new ExceptionEvent(0, ExistingCacheLocation)); + var existingCacheLocationVetted = RemovePrivacyInfo(ExistingCacheLocation); + Log.Debug($"Moved cache from {existingCacheLocationVetted} to {directoryPath}"); + TelemetryFactory.Get().Log("DevDriveInsights_PackageCacheMovedSuccessfully_Event", LogLevel.Critical, new ExceptionEvent(0, existingCacheLocationVetted)); } } else diff --git a/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsightsViewModel.cs b/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsightsViewModel.cs index 8f22618656..8868905063 100644 --- a/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsightsViewModel.cs +++ b/tools/Customization/DevHome.Customization/ViewModels/DevDriveInsightsViewModel.cs @@ -372,11 +372,7 @@ public void UpdateOptimizerListViewModelList() continue; } - List existingDevDriveLetters = new List(); - foreach (var existingDevDrive in ExistingDevDrives) - { - existingDevDriveLetters.Add(existingDevDrive.DriveLetter.ToString()); - } + List existingDevDriveLetters = ExistingDevDrives.Select(x => x.DriveLetter.ToString()).ToList(); var exampleDirectory = Path.Join(existingDevDriveLetters[0] + ":", cache.ExampleSubDirectory); var card = new DevDriveOptimizerCardViewModel(