-
Notifications
You must be signed in to change notification settings - Fork 896
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
(#3194) Add command to purge cached queries
The changes in this commit adds a new command to Chocolatey CLI that allows the user to clear any cached queries that have been saved on their system. This will clear both system and user level caches when running as an administrator, and user level caches when running in a non-elevated context. Additionally, the ability to only remove expired caches is added as well as just listing how many items has been cached.
- Loading branch information
1 parent
33d78b6
commit f6baa19
Showing
4 changed files
with
361 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
307 changes: 307 additions & 0 deletions
307
src/chocolatey/infrastructure.app/commands/ChocolateyCacheCommand.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,307 @@ | ||
// Copyright © 2023 Chocolatey Software, Inc | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
namespace chocolatey.infrastructure.app.commands | ||
{ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Linq; | ||
using chocolatey.infrastructure.app.attributes; | ||
using chocolatey.infrastructure.app.configuration; | ||
using chocolatey.infrastructure.app.domain; | ||
using chocolatey.infrastructure.app.nuget; | ||
using chocolatey.infrastructure.commandline; | ||
using chocolatey.infrastructure.commands; | ||
using chocolatey.infrastructure.filesystem; | ||
using chocolatey.infrastructure.logging; | ||
|
||
[CommandFor("cache", "Manipulate the HTTP caches that are used to store information", Version = "2.1.0")] | ||
public class ChocolateyCacheCommand : ChocolateyCommandBase, ICommand | ||
{ | ||
private readonly IFileSystem _fileSystem; | ||
|
||
public ChocolateyCacheCommand(IFileSystem fileSystem) | ||
{ | ||
_fileSystem = fileSystem; | ||
} | ||
|
||
public virtual void ConfigureArgumentParser(OptionSet optionSet, ChocolateyConfiguration configuration) | ||
{ | ||
optionSet | ||
.Add("expired", | ||
"Expired - Remove cached items that has expired.", | ||
option => configuration.CacheCommand.RemoveExpiredItemsOnly = option != null); | ||
} | ||
|
||
public virtual void DryRun(ChocolateyConfiguration configuration) | ||
{ | ||
Run(configuration); | ||
} | ||
|
||
public virtual bool MayRequireAdminAccess() | ||
{ | ||
// We will support cleaning the user cache directory without cleaning the system directory. | ||
// As such it can be run without admin access. | ||
return false; | ||
} | ||
|
||
public virtual void ParseAdditionalArguments(IList<string> unparsedArguments, ChocolateyConfiguration configuration) | ||
{ | ||
CacheCommandType command; | ||
|
||
if (unparsedArguments.Count == 0) | ||
{ | ||
command = CacheCommandType.List; | ||
} | ||
else if (!Enum.TryParse(unparsedArguments[0], true, out command)) | ||
{ | ||
this.Log().Warn("Unknown cache command '{0}'. Setting to list.", unparsedArguments[0]); | ||
command = CacheCommandType.List; | ||
} | ||
|
||
configuration.CacheCommand.Command = command; | ||
} | ||
|
||
public virtual void Run(ChocolateyConfiguration config) | ||
{ | ||
switch (config.CacheCommand.Command) | ||
{ | ||
case CacheCommandType.List: | ||
ListCacheStatistic(config); | ||
break; | ||
|
||
case CacheCommandType.Remove: | ||
RemoveCachedItems(config); | ||
break; | ||
} | ||
} | ||
|
||
public virtual void Validate(ChocolateyConfiguration configuration) | ||
{ | ||
// Nothing to validate | ||
} | ||
|
||
protected void CleanCachedItemsInPath(ChocolateyConfiguration configuration, string cacheLocation) | ||
{ | ||
if (configuration.Noop && configuration.CacheCommand.RemoveExpiredItemsOnly) | ||
{ | ||
this.Log().Info("Would remove all files with the .dat extension older than 30 minutes in '{0}'", cacheLocation); | ||
return; | ||
} | ||
else if (configuration.Noop) | ||
{ | ||
this.Log().Info("Would remove all files with the .dat in '{0}'", cacheLocation); | ||
return; | ||
} | ||
|
||
var expirationTimer = GetCacheExpiration(configuration); | ||
|
||
var filesBeforeClean = _fileSystem.GetFiles(cacheLocation, "*.dat", SearchOption.AllDirectories); | ||
|
||
if (configuration.CacheCommand.RemoveExpiredItemsOnly) | ||
{ | ||
filesBeforeClean = filesBeforeClean.Where(f => _fileSystem.GetFileModifiedDate(f) < expirationTimer); | ||
} | ||
|
||
var beforeFilesCount = filesBeforeClean.Count(); | ||
|
||
if (beforeFilesCount == 0) | ||
{ | ||
this.Log().Info("No cached items available to be removed in '{0}'.", cacheLocation); | ||
return; | ||
} | ||
|
||
if (configuration.CacheCommand.RemoveExpiredItemsOnly) | ||
{ | ||
// We need to remove each individual file when the user only request | ||
// deleting expired items. This takes a bit longer. | ||
foreach (var fileToRemove in filesBeforeClean) | ||
{ | ||
_fileSystem.DeleteFile(fileToRemove); | ||
} | ||
|
||
foreach (var directoryToRemove in _fileSystem.GetDirectories(cacheLocation)) | ||
{ | ||
if (!_fileSystem.GetFiles(directoryToRemove, "*", SearchOption.AllDirectories).Any()) | ||
{ | ||
_fileSystem.DeleteDirectoryChecked(directoryToRemove, recursive: false, overrideAttributes: false, isSilent: true); | ||
} | ||
} | ||
} | ||
else | ||
{ | ||
foreach (var directoryToRemove in _fileSystem.GetDirectories(cacheLocation)) | ||
{ | ||
_fileSystem.DeleteDirectoryChecked(directoryToRemove, recursive: true); | ||
} | ||
} | ||
|
||
var filesAfterClean = _fileSystem.GetFiles(cacheLocation, "*.dat", SearchOption.AllDirectories); | ||
|
||
if (configuration.CacheCommand.RemoveExpiredItemsOnly) | ||
{ | ||
filesAfterClean = filesAfterClean.Where(f => _fileSystem.GetFileModifiedDate(f) < expirationTimer); | ||
|
||
this.Log().Info("Removed {0} expired cached items in '{1}'", beforeFilesCount - filesAfterClean.Count(), cacheLocation); | ||
} | ||
else | ||
{ | ||
this.Log().Info("Removed {0} cached items in '{1}'", beforeFilesCount - filesAfterClean.Count(), cacheLocation); | ||
} | ||
} | ||
|
||
protected override string GetCommandDescription(CommandForAttribute attribute) | ||
{ | ||
return @"Get the statistics of what Chocolatey has cached, or clear any cached | ||
items in the current context. | ||
This is helpful for when it is necessary for the user to figure out | ||
when the correct version of a package is not installed, but instead a | ||
different version is used."; | ||
} | ||
|
||
protected override IEnumerable<string> GetCommandExamples(CommandForAttribute[] attributes) | ||
{ | ||
return new[] | ||
{ | ||
"choco cache", | ||
"choco cache list", | ||
"choco cache remove", | ||
"choco cache remove --expired" | ||
}; | ||
} | ||
|
||
protected override IEnumerable<string> GetCommandUsage(CommandForAttribute[] attributes) | ||
{ | ||
yield return "choco cache [list]|remove [options/switches]"; | ||
} | ||
protected virtual void ListCacheStatistic(ChocolateyConfiguration configuration) | ||
{ | ||
var userCacheLocation = ApplicationParameters.HttpCacheUserLocation; | ||
var systemCacheLocation = ApplicationParameters.HttpCacheLocation; | ||
|
||
if (userCacheLocation != systemCacheLocation) | ||
{ | ||
this.Log().Info(ChocolateyLoggers.Important, "System Level HTTP Cache"); | ||
ListCachedItems(configuration, systemCacheLocation); | ||
|
||
this.Log().Info(string.Empty); | ||
this.Log().Info(ChocolateyLoggers.Important, "User Level HTTP Cache"); | ||
ListCachedItems(configuration, userCacheLocation); | ||
} | ||
else | ||
{ | ||
this.Log().Info(ChocolateyLoggers.Important, "User Level HTTP Cache"); | ||
ListCachedItems(configuration, userCacheLocation); | ||
} | ||
} | ||
|
||
protected virtual void RemoveCachedItems(ChocolateyConfiguration configuration) | ||
{ | ||
var systemCacheLocation = ApplicationParameters.HttpCacheLocation; | ||
var userCacheLocation = ApplicationParameters.HttpCacheUserLocation; | ||
|
||
this.Log().Info(ChocolateyLoggers.Important, "Cache cleanup"); | ||
|
||
if (userCacheLocation != systemCacheLocation) | ||
{ | ||
CleanCachedItemsInPath(configuration, systemCacheLocation); | ||
CleanCachedItemsInPath(configuration, userCacheLocation); | ||
} | ||
else | ||
{ | ||
CleanCachedItemsInPath(configuration, userCacheLocation); | ||
|
||
this.Log().Info(string.Empty); | ||
this.Log().Warn("To remove system level HTTP cache, run the same command as an adiministrator!"); | ||
} | ||
} | ||
|
||
private static DateTime GetCacheExpiration(ChocolateyConfiguration configuration) | ||
{ | ||
DateTime? expirationTimer; | ||
var cacheContext = new ChocolateySourceCacheContext(configuration); | ||
|
||
if (cacheContext.MaxAge.HasValue) | ||
{ | ||
expirationTimer = cacheContext.MaxAge.Value.DateTime; | ||
} | ||
else | ||
{ | ||
expirationTimer = DateTime.Now.Subtract(cacheContext.MaxAgeTimeSpan); | ||
} | ||
|
||
return expirationTimer.Value; | ||
} | ||
|
||
private void ListCachedItems(ChocolateyConfiguration configuration, string cacheLocation) | ||
{ | ||
var cachedFiles = _fileSystem.GetFiles(cacheLocation, "*.dat", SearchOption.AllDirectories); | ||
var cachedDirectories = _fileSystem.GetDirectories(cacheLocation); | ||
var expirationTimer = GetCacheExpiration(configuration); | ||
|
||
var expiredFiles = cachedFiles.Where(f => _fileSystem.GetFileModifiedDate(f) < expirationTimer); | ||
|
||
this.Log().Info("We found {0} cached sources.", cachedDirectories.Count()); | ||
this.Log().Info("We found {0} cached items for all sources, where {1} items has expired.", cachedFiles.Count(), expiredFiles.Count()); | ||
} | ||
#region Obsoleted methods | ||
|
||
[Obsolete("Will be removed in v3. Use ConfigureArgumentParser instead!")] | ||
public void configure_argument_parser(OptionSet optionSet, ChocolateyConfiguration configuration) | ||
{ | ||
ConfigureArgumentParser(optionSet, configuration); | ||
} | ||
|
||
[Obsolete("Will be removed in v3. Use ParseAdditionalArguments instead!")] | ||
public void handle_additional_argument_parsing(IList<string> unparsedArguments, ChocolateyConfiguration configuration) | ||
{ | ||
ParseAdditionalArguments(unparsedArguments, configuration); | ||
} | ||
|
||
[Obsolete("Will be removed in v3. Use Validate instead!")] | ||
public void handle_validation(ChocolateyConfiguration configuration) | ||
{ | ||
Validate(configuration); | ||
} | ||
|
||
[Obsolete("Will be removed in v3. Use HelpMessage instead!")] | ||
public void help_message(ChocolateyConfiguration configuration) | ||
{ | ||
HelpMessage(configuration); | ||
} | ||
|
||
[Obsolete("Will be removed in v3. Use MayRequireAdminAccess instead!")] | ||
public bool may_require_admin_access() | ||
{ | ||
return MayRequireAdminAccess(); | ||
} | ||
|
||
[Obsolete("Will be removed in v3. Use DryRun instead!")] | ||
public void noop(ChocolateyConfiguration configuration) | ||
{ | ||
DryRun(configuration); | ||
} | ||
|
||
[Obsolete("Will be removed in v3. Use Run instead!")] | ||
public void run(ChocolateyConfiguration config) | ||
{ | ||
Run(config); | ||
} | ||
|
||
#endregion Obsoleted methods | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.