diff --git a/src/chocolatey.resources/helpers/ChocolateyTabExpansion.ps1 b/src/chocolatey.resources/helpers/ChocolateyTabExpansion.ps1
index cbd9fd908f..e63070ae1f 100644
--- a/src/chocolatey.resources/helpers/ChocolateyTabExpansion.ps1
+++ b/src/chocolatey.resources/helpers/ChocolateyTabExpansion.ps1
@@ -49,7 +49,7 @@ $commandOptions = @{
info = "--cert='' --certpassword='' --disable-repository-optimizations --include-configured-sources --local-only --password='' --prerelease --source='' --user='' --version=''"
install = "--allow-downgrade --allow-empty-checksums --allow-empty-checksums-secure --apply-args-to-dependencies --apply-package-parameters-to-dependencies --cert='' --certpassword='' --disable-repository-optimizations --download-checksum='' --download-checksum-x64='' --download-checksum-type='' --download-checksum-type-x64='' --exit-when-reboot-detected --force-dependencies --forcex86 --ignore-checksum --ignore-dependencies --ignore-detected-reboot --ignore-package-exit-codes --include-configured-sources --install-arguments='' --not-silent --override-arguments --package-parameters='' --password='' --pin --prerelease --require-checksums --skip-hooks --skip-scripts --source='' --stop-on-first-failure --use-package-exit-codes --user='' --version=''"
license = ""
- list = "--by-id-only --by-tag-only --detail --exact --id-only --id-starts-with --ignore-pinned --include-programs --page='' --page-size='' --prerelease --source='' --version=''"
+ list = "--by-id-only --by-tag-only --detail --exact --id-only --id-starts-with --ignore-pinned --include-programs --order-by-last-updated-date --page='' --page-size='' --prerelease --show-last-updated-date --source='' --version=''"
new = "--automaticpackage --download-checksum='' --download-checksum-x64='' --download-checksum-type='' --maintainer='' --name='' --output-directory='' --template='' --use-built-in-template --version=''"
outdated = "--cert='' --certpassword='' --disable-repository-optimizations --ignore-pinned --ignore-unfound --include-configured-sources --password='' --prerelease --source='' --user=''"
pack = "--output-directory='' --version=''"
@@ -242,9 +242,9 @@ function Get-ChocoOrderByOptions {
manually when the enum changes.
.OUTPUTS
- A string in the format "Id|LastPublished|Popularity|Title|Unsorted"
+ A string in the format "Id|LastPublished|Popularity|Title|Unsorted|LastUpdated"
#>
- return @("Id", "LastPublished", "Popularity", "Title", "Unsorted")
+ return @("Id", "LastPublished", "Popularity", "Title", "Unsorted", "LastUpdated")
}
function ChocolateyTabExpansion($lastBlock) {
diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyListCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyListCommand.cs
index e380aca4cf..d6134952e7 100644
--- a/src/chocolatey/infrastructure.app/commands/ChocolateyListCommand.cs
+++ b/src/chocolatey/infrastructure.app/commands/ChocolateyListCommand.cs
@@ -138,7 +138,13 @@ public virtual void ConfigureArgumentParser(OptionSet optionSet, ChocolateyConfi
option => configuration.Verbose = option != null)
.Add("ignore-pinned",
"Ignore Pinned - Ignore pinned packages. Defaults to false.",
- option => configuration.ListCommand.IgnorePinned = option != null);
+ option => configuration.ListCommand.IgnorePinned = option != null)
+ .Add("show-last-updated-date",
+ "Show Date - Shows the last date the package was installed/updated.",
+ option => configuration.ListCommand.ShowLastUpdatedDate = option != null)
+ .Add("order-by-last-updated-date",
+ "Order by Last Updated Date - Orders packages by date the package was installed/updated.",
+ option => configuration.ListCommand.OrderBy = PackageOrder.LastUpdated);
}
public virtual int Count(ChocolateyConfiguration config)
diff --git a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs
index c0bfe9f9c0..ad0ddf7040 100644
--- a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs
+++ b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs
@@ -626,6 +626,7 @@ public ListCommandConfiguration()
public bool ByTagOnly { get; set; }
public bool IdStartsWith { get; set; }
public bool IgnorePinned { get; set; }
+ public bool ShowLastUpdatedDate { get; set; }
public PackageOrder OrderBy { get; set; }
[Obsolete("This property is deprecated and will be removed in version 3.0. Use the 'OrderBy' property instead.")]
diff --git a/src/chocolatey/infrastructure.app/domain/ChocolateyPackageInformation.cs b/src/chocolatey/infrastructure.app/domain/ChocolateyPackageInformation.cs
index 697675b5aa..d5d90ac3d0 100644
--- a/src/chocolatey/infrastructure.app/domain/ChocolateyPackageInformation.cs
+++ b/src/chocolatey/infrastructure.app/domain/ChocolateyPackageInformation.cs
@@ -38,5 +38,6 @@ public ChocolateyPackageInformation(IPackageMetadata package)
public string ExtraInformation { get; set; }
public string DeploymentLocation { get; set; }
public string SourceInstalledFrom { get; set; }
+ public string LastUpdated { get; set; }
}
}
diff --git a/src/chocolatey/infrastructure.app/domain/PackageOrder.cs b/src/chocolatey/infrastructure.app/domain/PackageOrder.cs
index 0db14a85b9..2b93c3d30a 100644
--- a/src/chocolatey/infrastructure.app/domain/PackageOrder.cs
+++ b/src/chocolatey/infrastructure.app/domain/PackageOrder.cs
@@ -38,6 +38,11 @@ public enum PackageOrder
///
LastPublished,
+ ///
+ /// Sort by last updated date, from newest to oldest.
+ ///
+ LastUpdated,
+
///
/// Do not sort; return packages in the order received from the source.
///
diff --git a/src/chocolatey/infrastructure.app/nuget/NugetList.cs b/src/chocolatey/infrastructure.app/nuget/NugetList.cs
index f91762c2e2..ee03a640df 100644
--- a/src/chocolatey/infrastructure.app/nuget/NugetList.cs
+++ b/src/chocolatey/infrastructure.app/nuget/NugetList.cs
@@ -22,6 +22,7 @@
using chocolatey.infrastructure.tolerance;
using chocolatey.infrastructure.app.configuration;
using chocolatey.infrastructure.filesystem;
+using chocolatey.infrastructure.app.services;
using NuGet.Common;
using NuGet.Packaging;
using NuGet.Packaging.Core;
@@ -36,6 +37,8 @@ public static class NugetList
public static bool ThresholdHit { get; private set; }
public static bool LowerThresholdHit { get; private set; }
+ private const string LastUpdated = ".lastUpdated";
+
public static IEnumerable GetPackages(ChocolateyConfiguration configuration, ILogger nugetLogger, IFileSystem filesystem)
{
return SearchPackagesAsync(configuration, nugetLogger, filesystem).GetAwaiter().GetResult();
@@ -320,7 +323,7 @@ private async static Task> SearchPackagesAsyn
results = results.Where(p => (p.IsDownloadCacheAvailable && configuration.Information.IsLicensedVersion) || p.PackageTestResultStatus != "Failing").ToHashSet();
}
- results = ApplyPackageSort(results, configuration.ListCommand.OrderBy).ToHashSet();
+ results = ApplyPackageSort(results, configuration.ListCommand.OrderBy, filesystem).ToHashSet();
return results.AsQueryable();
}
@@ -453,7 +456,7 @@ public static IPackageSearchMetadata FindPackage(
return packagesList.OrderByDescending(p => p.Identity.Version).FirstOrDefault();
}
- private static IOrderedEnumerable ApplyPackageSort(IEnumerable query, domain.PackageOrder orderBy)
+ private static IOrderedEnumerable ApplyPackageSort(IEnumerable query, domain.PackageOrder orderBy, IFileSystem filesystem)
{
switch (orderBy)
{
@@ -481,6 +484,31 @@ private static IOrderedEnumerable ApplyPackageSort(IEnum
.ThenBy(q => q.Identity.Id)
.ThenByDescending(q => q.Identity.Version);
+ case domain.PackageOrder.LastUpdated:
+ return query
+ .OrderByDescending(q =>
+ {
+ string lastUpdatedContent = null;
+ var pkgStorePath = ChocolateyPackageInformationService.GetStorePath(filesystem, q.Identity.Id, q.Identity.Version);
+ var lastUpdated = filesystem.CombinePaths(pkgStorePath, LastUpdated);
+ if (filesystem.FileExists(lastUpdated))
+ {
+ FaultTolerance.TryCatchWithLoggingException(
+ () =>
+ {
+ lastUpdatedContent = filesystem.ReadFile(lastUpdated);
+ },
+ "Unable to read last updated from file",
+ throwError: false,
+ logWarningInsteadOfError: true
+ );
+ }
+ return lastUpdatedContent;
+ })
+ .ThenBy(q => q.Identity.Id)
+ .ThenByDescending(q => q.Identity.Version);
+
+
default:
// Since we return an IOrderedEnumerable, some form of ordering must be applied,
// even when the user has not explicitly requested a sort order.
diff --git a/src/chocolatey/infrastructure.app/services/ChocolateyPackageInformationService.cs b/src/chocolatey/infrastructure.app/services/ChocolateyPackageInformationService.cs
index 3682325228..6be2845602 100644
--- a/src/chocolatey/infrastructure.app/services/ChocolateyPackageInformationService.cs
+++ b/src/chocolatey/infrastructure.app/services/ChocolateyPackageInformationService.cs
@@ -46,6 +46,7 @@ public class ChocolateyPackageInformationService : IChocolateyPackageInformation
private const string VersionOverrideFile = ".version";
private const string DeploymentLocationFile = ".deploymentLocation";
private const string SourceInstalledFromFile = ".sourceInstalledFrom";
+ private const string LastUpdated = ".lastUpdated";
// We need to store the package identifiers we have warned about
// to prevent duplicated outputs.
@@ -208,6 +209,20 @@ has errored attempting to read it. This file will be renamed to
logWarningInsteadOfError: true
);
}
+
+ var lastUpdated = _fileSystem.CombinePaths(pkgStorePath, LastUpdated);
+ if (_fileSystem.FileExists(lastUpdated))
+ {
+ FaultTolerance.TryCatchWithLoggingException(
+ () =>
+ {
+ packageInformation.LastUpdated = _fileSystem.ReadFile(lastUpdated);
+ },
+ "Unable to read last updated from file",
+ throwError: false,
+ logWarningInsteadOfError: true
+ );
+ }
return packageInformation;
}
@@ -340,6 +355,23 @@ public void Save(ChocolateyPackageInformation packageInformation)
{
_fileSystem.DeleteFile(_fileSystem.CombinePaths(pkgStorePath, SourceInstalledFromFile));
}
+
+ if (!string.IsNullOrWhiteSpace(packageInformation.LastUpdated))
+ {
+ var lastUpdatedDate = _fileSystem.CombinePaths(pkgStorePath, LastUpdated);
+ if (_fileSystem.FileExists(lastUpdatedDate))
+ {
+ _fileSystem.DeleteFile(lastUpdatedDate);
+ }
+
+ _fileSystem.WriteFile(lastUpdatedDate, packageInformation.LastUpdated);
+ }
+ else
+ {
+ _fileSystem.DeleteFile(_fileSystem.CombinePaths(pkgStorePath, LastUpdated));
+ }
+
+
}
public void Remove(IPackageMetadata package)
@@ -353,7 +385,7 @@ public void Remove(IPackageMetadata package)
_fileSystem.DeleteDirectoryChecked(pkgStorePath, recursive: true);
}
- private static string GetStorePath(IFileSystem fileSystem, string id, NuGetVersion version)
+ internal static string GetStorePath(IFileSystem fileSystem, string id, NuGetVersion version)
{
var preferredStorePath = fileSystem.CombinePaths(ApplicationParameters.ChocolateyPackageInfoStoreLocation, "{0}.{1}".FormatWith(id, version.ToNormalizedStringChecked()));
diff --git a/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs b/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs
index 14742a25a2..e10fdee351 100644
--- a/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs
+++ b/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs
@@ -566,6 +566,7 @@ public virtual void HandlePackageResult(PackageResult packageResult, ChocolateyC
pkgInfo.DeploymentLocation = Environment.GetEnvironmentVariable(EnvironmentVariables.Package.ChocolateyPackageInstallLocation);
pkgInfo.SourceInstalledFrom = packageResult.SourceInstalledFrom;
+ pkgInfo.LastUpdated = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
UpdatePackageInformation(pkgInfo);
EnsureBadPackagesPathIsClean(packageResult);
diff --git a/src/chocolatey/infrastructure.app/services/NugetService.cs b/src/chocolatey/infrastructure.app/services/NugetService.cs
index 7540f58abc..080f2f0637 100644
--- a/src/chocolatey/infrastructure.app/services/NugetService.cs
+++ b/src/chocolatey/infrastructure.app/services/NugetService.cs
@@ -219,6 +219,7 @@ it is possible that incomplete package lists are returned from a command
string packageInstallLocation = null;
string deploymentlocation = null;
string sourceInstalledFrom = null;
+ string lastUpdated = null;
if (package.PackagePath != null && !string.IsNullOrWhiteSpace(package.PackagePath))
{
@@ -240,6 +241,7 @@ it is possible that incomplete package lists are returned from a command
packageInfo = _packageInfoService.Get(packageLocalMetadata);
deploymentlocation = packageInfo.DeploymentLocation;
sourceInstalledFrom = packageInfo.SourceInstalledFrom;
+ lastUpdated = packageInfo.LastUpdated;
if (config.ListCommand.IncludeVersionOverrides)
{
@@ -281,11 +283,12 @@ it is possible that incomplete package lists are returned from a command
{
if (!(packageInfo != null && packageInfo.IsPinned && config.ListCommand.IgnorePinned))
{
- this.Log().Info(logger, () => "{0}{1}".FormatWith(package.Identity.Id, config.ListCommand.IdOnly ? string.Empty : " {0}{1}{2}{3}".FormatWith(
+ this.Log().Info(logger, () => "{0}{1}".FormatWith(package.Identity.Id, config.ListCommand.IdOnly ? string.Empty : " {0}{1}{2}{3}{4}".FormatWith(
packageLocalMetadata != null ? packageLocalMetadata.Version.ToFullStringChecked() : package.Identity.Version.ToFullStringChecked(),
package.IsApproved ? " [Approved]" : string.Empty,
package.IsDownloadCacheAvailable ? " Downloads cached for licensed users" : string.Empty,
- package.PackageTestResultStatus == "Failing" && package.IsDownloadCacheAvailable ? " - Possibly broken for FOSS users (due to original download location changes by vendor)" : package.PackageTestResultStatus == "Failing" ? " - Possibly broken" : string.Empty
+ package.PackageTestResultStatus == "Failing" && package.IsDownloadCacheAvailable ? " - Possibly broken for FOSS users (due to original download location changes by vendor)" : package.PackageTestResultStatus == "Failing" ? " - Possibly broken" : string.Empty,
+ config.ListCommand.ShowLastUpdatedDate ? " {0}".FormatWith(packageInfo.LastUpdated) ?? " Last updated not available" : string.Empty
))
);
@@ -299,7 +302,7 @@ Package url{6}
Tags: {9}
Software Site: {10}
Software License: {11}{12}{13}{14}{15}{16}
- Description: {17}{18}{19}{20}{21}
+ Description: {17}{18}{19}{20}{21}{22}
".FormatWith(
package.Title.EscapeCurlyBraces(),
package.Published.GetValueOrDefault().UtcDateTime.ToShortDateString(),
@@ -336,6 +339,7 @@ Package url{6}
!string.IsNullOrWhiteSpace(package.ReleaseNotes.ToStringSafe()) ? "{0} Release Notes: {1}".FormatWith(Environment.NewLine, package.ReleaseNotes.EscapeCurlyBraces().Replace("\n ", "\n").Replace("\n", "\n ")) : string.Empty,
!string.IsNullOrWhiteSpace(deploymentlocation) ? "{0} Deployed to: '{1}'".FormatWith(Environment.NewLine, deploymentlocation) :string.Empty,
!string.IsNullOrWhiteSpace(sourceInstalledFrom) ? "{0} Source package was installed from: '{1}'".FormatWith(Environment.NewLine, sourceInstalledFrom) : string.Empty,
+ !string.IsNullOrWhiteSpace(lastUpdated) ? "{0} Last updated: {1}".FormatWith(Environment.NewLine, lastUpdated) : string.Empty,
packageArgumentsUnencrypted != null ? packageArgumentsUnencrypted : string.Empty
));
}
@@ -361,7 +365,7 @@ Package url{6}
}
else
{
- yield return new PackageResult(packageLocalMetadata, package, config.ListCommand.LocalOnly ? packageInstallLocation : null, config.Sources, null);
+ yield return new PackageResult(packageLocalMetadata, package, config.ListCommand.LocalOnly ? packageInstallLocation : null, config.Sources, null, lastUpdated);
}
}
@@ -1001,7 +1005,7 @@ Version was specified as '{0}'. It is possible that version
packageRemoteMetadata.PackageTestResultStatus == "Failing" && packageRemoteMetadata.IsDownloadCacheAvailable ? " - Likely broken for FOSS users (due to download location changes)" : packageRemoteMetadata.PackageTestResultStatus == "Failing" ? " - Possibly broken" : string.Empty
));
- var packageResult = packageResultsToReturn.GetOrAdd(packageDependencyInfo.Id.ToLowerSafe(), new PackageResult(packageMetadata, packageRemoteMetadata, installedPath, null, packageDependencyInfo.Source.ToStringSafe()));
+ var packageResult = packageResultsToReturn.GetOrAdd(packageDependencyInfo.Id.ToLowerSafe(), new PackageResult(packageMetadata, packageRemoteMetadata, installedPath, null, packageDependencyInfo.Source.ToStringSafe(), null));
if (shouldAddForcedResultMessage)
{
packageResult.Messages.Add(new ResultMessage(ResultType.Note, "Backing up and removing old version"));
@@ -1826,7 +1830,7 @@ public virtual ConcurrentDictionary Upgrade(ChocolateyCon
packageRemoteMetadata.PackageTestResultStatus == "Failing" && packageRemoteMetadata.IsDownloadCacheAvailable ? " - Likely broken for FOSS users (due to download location changes)" : packageRemoteMetadata.PackageTestResultStatus == "Failing" ? " - Possibly broken" : string.Empty
));
- var upgradePackageResult = packageResultsToReturn.GetOrAdd(packageDependencyInfo.Id.ToLowerSafe(), new PackageResult(packageMetadata, packageRemoteMetadata, installedPath, null, packageDependencyInfo.Source.ToStringSafe()));
+ var upgradePackageResult = packageResultsToReturn.GetOrAdd(packageDependencyInfo.Id.ToLowerSafe(), new PackageResult(packageMetadata, packageRemoteMetadata, installedPath, null, packageDependencyInfo.Source.ToStringSafe(), null));
upgradePackageResult.ResetMetadata(packageMetadata, packageRemoteMetadata);
upgradePackageResult.InstallLocation = installedPath;
@@ -2902,7 +2906,7 @@ protected virtual void BackupAndRunBeforeModify(
{
"chocolatey".Log().Debug("Running beforeModify step for '{0}'", packageResult.PackageMetadata.Id);
- var packageResultCopy = new PackageResult(packageResult.PackageMetadata, packageResult.SearchMetadata, packageResult.InstallLocation, packageResult.Source, null);
+ var packageResultCopy = new PackageResult(packageResult.PackageMetadata, packageResult.SearchMetadata, packageResult.InstallLocation, packageResult.Source, null, null);
beforeModifyAction(packageResultCopy, config);
diff --git a/src/chocolatey/infrastructure/results/PackageResult.cs b/src/chocolatey/infrastructure/results/PackageResult.cs
index a64d5f8d7a..76295d5c31 100644
--- a/src/chocolatey/infrastructure/results/PackageResult.cs
+++ b/src/chocolatey/infrastructure/results/PackageResult.cs
@@ -71,6 +71,11 @@ public bool Warning
/// The package source used to install the package.
///
public string SourceInstalledFrom { get; set; }
+
+ ///
+ /// When the packaage was last updated.
+ ///
+ public string LastUpdated { get; set; }
public int ExitCode { get; set; }
public void ResetMetadata(IPackageMetadata metadata, IPackageSearchMetadata search)
@@ -141,8 +146,8 @@ public PackageResult(IPackageSearchMetadata packageSearch, string installLocatio
}
[Obsolete("This overload is deprecated and will be removed in v3.")]
- public PackageResult(IPackageMetadata packageMetadata, IPackageSearchMetadata packageSearch, string installLocation, string source = null)
- : this(packageMetadata, packageSearch, installLocation, source, null) { }
+ public PackageResult(IPackageMetadata packageMetadata, IPackageSearchMetadata packageSearch, string installLocation, string source = null, string lastUpdated = null)
+ : this(packageMetadata, packageSearch, installLocation, source, null, lastUpdated) { }
///
/// Initializes a new instance of the class.
@@ -152,12 +157,14 @@ public PackageResult(IPackageMetadata packageMetadata, IPackageSearchMetadata pa
/// Assigned to
/// Sources available during package installation. Assigned to
/// The package source used to install the package. Assigned to
- public PackageResult(IPackageMetadata packageMetadata, IPackageSearchMetadata packageSearch, string installLocation, string source, string sourceInstalledFrom)
- : this(packageMetadata.Id, packageMetadata.Version.ToNormalizedStringChecked(), installLocation)
+ /// The last updated date of the package. Assigned to
+ public PackageResult(IPackageMetadata packageMetadata, IPackageSearchMetadata packageSearch, string installLocation, string source, string sourceInstalledFrom, string lastUpdated)
+ : this(packageMetadata.Id, packageMetadata.Version.ToNormalizedStringChecked(), installLocation, lastUpdated)
{
SearchMetadata = packageSearch;
PackageMetadata = packageMetadata;
SourceInstalledFrom = sourceInstalledFrom;
+ LastUpdated = lastUpdated;
var sources = new List();
if (!string.IsNullOrEmpty(source))
{