| File: Commands\CacheCommand.cs | Web Access |
| Project: src\src\Aspire.Cli\Aspire.Cli.csproj (aspire) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.CommandLine; using System.Globalization; using Aspire.Cli.Interaction; using Aspire.Cli.Resources; using Aspire.Cli.Utils; namespace Aspire.Cli.Commands; internal sealed class CacheCommand : ParentCommand { internal override HelpGroup HelpGroup => HelpGroup.ToolsAndConfiguration; public CacheCommand( CommonCommandServices services) : base("cache", CacheCommandStrings.Description, services) { var clearCommand = new ClearCommand(services); Subcommands.Add(clearCommand); } internal sealed class ClearCommand : BaseCommand { public ClearCommand(CommonCommandServices services) : base("clear", CacheCommandStrings.ClearCommand_Description, services) { } protected override Task<CommandResult> ExecuteAsync(ParseResult parseResult, CancellationToken cancellationToken) { try { var filesDeleted = 0; var currentLogFilePath = ExecutionContext.LogFilePath; filesDeleted += ClearDirectoryContents(ExecutionContext.CacheDirectory); filesDeleted += ClearDirectoryContents(ExecutionContext.SdksDirectory); filesDeleted += ClearDirectoryContents(ExecutionContext.PackagesDirectory); // Wipe the staging NuGet package cache too. Producers (PrebuiltAppHostServer's // temporary nuget.config for the staging channel) deposit SHA-keyed package // caches under <ASPIRE_HOME>/.nugetpackages/<sha>; clearing them lets users // recover wedged staging restores without filesystem surgery. We hand the parent // directory to ClearDirectoryContents so each SHA subdirectory is wiped while // the parent itself stays in place for the next staging restore. filesDeleted += ClearDirectoryContents( new DirectoryInfo(CliPathHelper.GetStagingNuGetPackagesDirectory(ExecutionContext.AspireHomeDirectory))); filesDeleted += ClearDirectoryContents( ExecutionContext.LogsDirectory, skipFile: f => f.FullName.Equals(currentLogFilePath, StringComparison.OrdinalIgnoreCase)); if (filesDeleted == 0) { InteractionService.DisplayMessage(KnownEmojis.Information, CacheCommandStrings.CacheAlreadyEmpty); } else { InteractionService.DisplaySuccess(CacheCommandStrings.CacheCleared); } return Task.FromResult(CommandResult.Success()); } catch (Exception ex) { var errorMessage = string.Format(CultureInfo.CurrentCulture, CacheCommandStrings.CacheClearFailed, ex.Message); Telemetry.RecordError(errorMessage, ex); InteractionService.DisplayError(errorMessage); return Task.FromResult(CommandResult.Failure(CliExitCodes.InvalidCommand)); } } private static readonly EnumerationOptions s_enumerationOptions = new() { RecurseSubdirectories = true, IgnoreInaccessible = true }; internal static int ClearDirectoryContents(DirectoryInfo? directory, Func<FileInfo, bool>? skipFile = null) { if (directory is null || !directory.Exists) { return 0; } var filesDeleted = 0; foreach (var file in directory.EnumerateFiles("*", s_enumerationOptions)) { if (skipFile?.Invoke(file) == true) { continue; } try { file.Delete(); filesDeleted++; } catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or System.Security.SecurityException) { // Continue deleting other files even if some fail (e.g. locked by a running process) } } foreach (var subdirectory in directory.EnumerateDirectories()) { try { subdirectory.Delete(recursive: true); } catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or System.Security.SecurityException) { // Continue deleting other directories even if some fail } } return filesDeleted; } } }