From 3b9aec4ad324f9490a7c9617b2402c067d36e769 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Sat, 2 May 2015 10:42:49 -0500 Subject: [PATCH 01/27] (GH-110) Prevent multiple backups of same config --- src/chocolatey/infrastructure.app/services/NugetService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chocolatey/infrastructure.app/services/NugetService.cs b/src/chocolatey/infrastructure.app/services/NugetService.cs index ef7d1d7d64..532ac3943d 100644 --- a/src/chocolatey/infrastructure.app/services/NugetService.cs +++ b/src/chocolatey/infrastructure.app/services/NugetService.cs @@ -687,7 +687,7 @@ private void backup_configuration_files(string packageInstallPath, string versio var configFiles = _fileSystem.get_files(packageInstallPath, ApplicationParameters.ConfigFileExtensions, SearchOption.AllDirectories); foreach (var file in configFiles.or_empty_list_if_null()) { - var backupName = "{0}.{1}.{2}".format_with(_fileSystem.get_file_name_without_extension(file), version, _fileSystem.get_file_extension(file)); + var backupName = "{0}.{1}".format_with(_fileSystem.get_file_name(file), version); FaultTolerance.try_catch_with_logging_exception( () => _fileSystem.copy_file(file, _fileSystem.combine_paths(_fileSystem.get_directory_name(file), backupName), overwriteExisting: true), From 02ae5c2c0f4393ec18208e4d047c3d41e7431ea5 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Sat, 2 May 2015 10:49:22 -0500 Subject: [PATCH 02/27] (GH-257) Fix error deserializing corrupt snapshot Log a warning when the snapshot is corrupt but don't throw an error. This fixes the error `Error deserializing response of type chocolatey.infrastructure.app.domain.Registry: There is an error in XML document (10, 24).` --- .../ChocolateyPackageInformationService.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/chocolatey/infrastructure.app/services/ChocolateyPackageInformationService.cs b/src/chocolatey/infrastructure.app/services/ChocolateyPackageInformationService.cs index d68ec15267..e3b26a93d7 100644 --- a/src/chocolatey/infrastructure.app/services/ChocolateyPackageInformationService.cs +++ b/src/chocolatey/infrastructure.app/services/ChocolateyPackageInformationService.cs @@ -51,12 +51,16 @@ public ChocolateyPackageInformation get_package_information(IPackage package) return packageInformation; } - string registrySnapshotFile = _fileSystem.combine_paths(pkgStorePath, REGISTRY_SNAPSHOT_FILE); - if (_fileSystem.file_exists(registrySnapshotFile)) - { - packageInformation.RegistrySnapshot = _registryService.read_from_file(registrySnapshotFile); - } - + FaultTolerance.try_catch_with_logging_exception( + () => + { + packageInformation.RegistrySnapshot = _registryService.read_from_file(_fileSystem.combine_paths(pkgStorePath, REGISTRY_SNAPSHOT_FILE)); + }, + "Unable to read registry snapshot file", + throwError: false, + logWarningInsteadOfError: true + ); + packageInformation.HasSilentUninstall = _fileSystem.file_exists(_fileSystem.combine_paths(pkgStorePath, SILENT_UNINSTALLER_FILE)); packageInformation.IsSideBySide = _fileSystem.file_exists(_fileSystem.combine_paths(pkgStorePath, SIDE_BY_SIDE_FILE)); packageInformation.IsPinned = _fileSystem.file_exists(_fileSystem.combine_paths(pkgStorePath, PIN_FILE)); From 8a8bd498c2e4078328195926bb90db019ae1ba62 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Sat, 2 May 2015 11:07:19 -0500 Subject: [PATCH 03/27] (GH-253) Fix Zip ChecksumType is ignored Due to defaulting `$url64bit` to `$url`, `$checksumType` was converting to the 64 bit checksumtype, even if it was empty. Which causes checksumtype to default to md5, no matter what the checksumtype passed in is. Move the checksumType setting for x64 into the check if the urls for 32 bit and 64 bit are different. Additionally, ensure that the checksumtype64 is not empty. --- .../helpers/functions/Get-ChocolateyWebFile.ps1 | 5 +++-- .../helpers/functions/Install-ChocolateyZipPackage.ps1 | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/chocolatey.resources/helpers/functions/Get-ChocolateyWebFile.ps1 b/src/chocolatey.resources/helpers/functions/Get-ChocolateyWebFile.ps1 index 217d19a0c2..ad83d67d51 100644 --- a/src/chocolatey.resources/helpers/functions/Get-ChocolateyWebFile.ps1 +++ b/src/chocolatey.resources/helpers/functions/Get-ChocolateyWebFile.ps1 @@ -85,9 +85,10 @@ param( # only set if urls are different if ($url32bit -ne $url64bit) { $checksum = $checksum64 + if ($checkSumType64 -ne '') { + $checksumType = $checksumType64 + } } - - $checksumType = $checksumType64 } try { diff --git a/src/chocolatey.resources/helpers/functions/Install-ChocolateyZipPackage.ps1 b/src/chocolatey.resources/helpers/functions/Install-ChocolateyZipPackage.ps1 index 0d9cea1315..63223ba8be 100644 --- a/src/chocolatey.resources/helpers/functions/Install-ChocolateyZipPackage.ps1 +++ b/src/chocolatey.resources/helpers/functions/Install-ChocolateyZipPackage.ps1 @@ -63,7 +63,7 @@ param( [string] $packageName, [string] $url, [string] $unzipLocation, - [string] $url64bit = $url, + [string] $url64bit = '', [string] $specificFolder ="", [string] $checksum = '', [string] $checksumType = '', From 1dffc0a958a5d85fb0b1ec2506052a88190c01a7 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Sun, 3 May 2015 11:06:36 -0500 Subject: [PATCH 04/27] (maint) formatting --- src/chocolatey/infrastructure.app/services/RegistryService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/chocolatey/infrastructure.app/services/RegistryService.cs b/src/chocolatey/infrastructure.app/services/RegistryService.cs index c5af7ec06b..e5ba45371f 100644 --- a/src/chocolatey/infrastructure.app/services/RegistryService.cs +++ b/src/chocolatey/infrastructure.app/services/RegistryService.cs @@ -30,7 +30,7 @@ namespace chocolatey.infrastructure.app.services /// /// Allows comparing registry /// - public class RegistryService : IRegistryService + public sealed class RegistryService : IRegistryService { private readonly IXmlService _xmlService; private readonly IFileSystem _fileSystem; @@ -206,7 +206,7 @@ public bool value_exists(string keyPath, string value) { //todo: make this check less crazy... return get_installer_keys().RegistryKeys.Any(k => k.KeyPath == keyPath); - + //return Microsoft.Win32.Registry.GetValue(keyPath, value, null) != null; } From 07271f6e071f75bdbe6ef6a36bcd6d22683cf01f Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Sun, 3 May 2015 11:08:02 -0500 Subject: [PATCH 05/27] (GH-257) fix missing imports --- .../services/ChocolateyPackageInformationService.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/chocolatey/infrastructure.app/services/ChocolateyPackageInformationService.cs b/src/chocolatey/infrastructure.app/services/ChocolateyPackageInformationService.cs index e3b26a93d7..31a5a59721 100644 --- a/src/chocolatey/infrastructure.app/services/ChocolateyPackageInformationService.cs +++ b/src/chocolatey/infrastructure.app/services/ChocolateyPackageInformationService.cs @@ -15,10 +15,12 @@ namespace chocolatey.infrastructure.app.services { + using System; using System.IO; using System.Text; using NuGet; using domain; + using tolerance; using IFileSystem = filesystem.IFileSystem; internal class ChocolateyPackageInformationService : IChocolateyPackageInformationService From f5f24ad2da638bf089f2fb1b5559d6524bb4a272 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Sun, 3 May 2015 11:23:08 -0500 Subject: [PATCH 06/27] (GH-121) Add package files domain Define the package files classes and the package information file they will produce. --- src/chocolatey/chocolatey.csproj | 2 + .../infrastructure.app/domain/PackageFile.cs | 46 +++++++++++++ .../infrastructure.app/domain/PackageFiles.cs | 68 +++++++++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 src/chocolatey/infrastructure.app/domain/PackageFile.cs create mode 100644 src/chocolatey/infrastructure.app/domain/PackageFiles.cs diff --git a/src/chocolatey/chocolatey.csproj b/src/chocolatey/chocolatey.csproj index 87f6acf0ec..2cb7f6605e 100644 --- a/src/chocolatey/chocolatey.csproj +++ b/src/chocolatey/chocolatey.csproj @@ -94,6 +94,8 @@ + + diff --git a/src/chocolatey/infrastructure.app/domain/PackageFile.cs b/src/chocolatey/infrastructure.app/domain/PackageFile.cs new file mode 100644 index 0000000000..3131601b71 --- /dev/null +++ b/src/chocolatey/infrastructure.app/domain/PackageFile.cs @@ -0,0 +1,46 @@ +// Copyright © 2011 - Present RealDimensions Software, LLC +// +// 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.domain +{ + using System; + using System.Xml.Serialization; + + /// + /// A package file in the snapshot. + /// + [Serializable] + [XmlType("file")] + public sealed class PackageFile + { + /// + /// Gets or sets the path of the file. + /// + /// + /// The path. + /// + [XmlAttribute(AttributeName = "path")] + public string Path { get; set; } + + /// + /// Gets or sets the checksum, currently only md5 because fast computation. + /// + /// + /// The checksum. + /// + [XmlAttribute(AttributeName = "checksum")] + public string Checksum { get; set; } + } +} \ No newline at end of file diff --git a/src/chocolatey/infrastructure.app/domain/PackageFiles.cs b/src/chocolatey/infrastructure.app/domain/PackageFiles.cs new file mode 100644 index 0000000000..a36ca65c1a --- /dev/null +++ b/src/chocolatey/infrastructure.app/domain/PackageFiles.cs @@ -0,0 +1,68 @@ +// Copyright © 2011 - Present RealDimensions Software, LLC +// +// 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.domain +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Xml.Serialization; + + /// + /// The package files snapshot + /// + [Serializable] + [XmlType("fileSnapshot")] + public sealed class PackageFiles + { + /// + /// Initializes a new instance of the class. + /// + public PackageFiles() + : this(new HashSet()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The package files. + public PackageFiles(IEnumerable files) + { + Files = files != null ? files.ToList() : new List(); + } + + /// + /// Gets or sets the files. + /// + /// + /// The files. + /// + /// + /// On .NET 4.0, get error CS0200 when private set - see http://stackoverflow.com/a/23809226/18475 + /// + [XmlArray("files")] + public List Files { get; set; } + + /// + /// Gets or sets the package checksum. + /// + /// + /// The package checksum. + /// + [XmlElement(ElementName = "packageChecksum")] + public string PackageChecksum { get; set; } + } +} \ No newline at end of file From cd483be627c8359e3f2abddf4facbdd9cf1d2818 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Sun, 3 May 2015 11:24:55 -0500 Subject: [PATCH 07/27] (GH-121) Add files snapshot --- .../infrastructure.app/domain/ChocolateyPackageInformation.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/chocolatey/infrastructure.app/domain/ChocolateyPackageInformation.cs b/src/chocolatey/infrastructure.app/domain/ChocolateyPackageInformation.cs index 83fe777364..36ebce1296 100644 --- a/src/chocolatey/infrastructure.app/domain/ChocolateyPackageInformation.cs +++ b/src/chocolatey/infrastructure.app/domain/ChocolateyPackageInformation.cs @@ -26,6 +26,7 @@ public ChocolateyPackageInformation(IPackage package) public IPackage Package { get; set; } public Registry RegistrySnapshot { get; set; } + public PackageFiles FilesSnapshot { get; set; } public bool HasSilentUninstall { get; set; } public bool IsSideBySide { get; set; } public bool IsPinned { get; set; } From 148bf2656b04f2080b3b3c0bfc716cc4de0a327c Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Sun, 3 May 2015 11:26:43 -0500 Subject: [PATCH 08/27] (GH-121) Add files service Add ability to read and save package file information. --- src/chocolatey/chocolatey.csproj | 2 + .../registration/ContainerBinding.cs | 1 + .../services/FilesService.cs | 48 +++++++++++++++++++ .../services/IFilesService.cs | 25 ++++++++++ 4 files changed, 76 insertions(+) create mode 100644 src/chocolatey/infrastructure.app/services/FilesService.cs create mode 100644 src/chocolatey/infrastructure.app/services/IFilesService.cs diff --git a/src/chocolatey/chocolatey.csproj b/src/chocolatey/chocolatey.csproj index 2cb7f6605e..18b9ede0fa 100644 --- a/src/chocolatey/chocolatey.csproj +++ b/src/chocolatey/chocolatey.csproj @@ -104,6 +104,8 @@ + + diff --git a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs index 995a09b297..28fde79390 100644 --- a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs +++ b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs @@ -53,6 +53,7 @@ public void RegisterComponents(Container container) container.Register(Lifestyle.Singleton); container.Register(Lifestyle.Singleton); container.Register(Lifestyle.Singleton); + container.Register(Lifestyle.Singleton); container.Register(Lifestyle.Singleton); container.Register(Lifestyle.Singleton); container.Register(Lifestyle.Singleton); diff --git a/src/chocolatey/infrastructure.app/services/FilesService.cs b/src/chocolatey/infrastructure.app/services/FilesService.cs new file mode 100644 index 0000000000..c8e0adee6d --- /dev/null +++ b/src/chocolatey/infrastructure.app/services/FilesService.cs @@ -0,0 +1,48 @@ +// Copyright © 2011 - Present RealDimensions Software, LLC +// +// 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.services +{ + using domain; + using filesystem; + using infrastructure.services; + + public sealed class FilesService : IFilesService + { + private readonly IXmlService _xmlService; + private readonly IFileSystem _fileSystem; + + public FilesService(IXmlService xmlService, IFileSystem fileSystem) + { + _xmlService = xmlService; + _fileSystem = fileSystem; + } + + public PackageFiles read_from_file(string filePath) + { + if (!_fileSystem.file_exists(filePath)) + { + return null; + } + + return _xmlService.deserialize(filePath); + } + + public void save_to_file(PackageFiles snapshot, string filePath) + { + _xmlService.serialize(snapshot, filePath); + } + } +} \ No newline at end of file diff --git a/src/chocolatey/infrastructure.app/services/IFilesService.cs b/src/chocolatey/infrastructure.app/services/IFilesService.cs new file mode 100644 index 0000000000..daf42c7fcf --- /dev/null +++ b/src/chocolatey/infrastructure.app/services/IFilesService.cs @@ -0,0 +1,25 @@ +// Copyright © 2011 - Present RealDimensions Software, LLC +// +// 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.services +{ + using domain; + + public interface IFilesService + { + PackageFiles read_from_file(string filepath); + void save_to_file(PackageFiles snapshot, string filePath); + } +} \ No newline at end of file From f60ce80f20a90d29b0fc5f6ab4d2c0f39a12142d Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Sun, 3 May 2015 11:28:06 -0500 Subject: [PATCH 09/27] (GH-121) PkgInfoService files service interaction Add the interaction to the package information service to read the files file and save to the files file. --- .../ChocolateyPackageInformationService.cs | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/chocolatey/infrastructure.app/services/ChocolateyPackageInformationService.cs b/src/chocolatey/infrastructure.app/services/ChocolateyPackageInformationService.cs index 31a5a59721..d6cf21d4e0 100644 --- a/src/chocolatey/infrastructure.app/services/ChocolateyPackageInformationService.cs +++ b/src/chocolatey/infrastructure.app/services/ChocolateyPackageInformationService.cs @@ -27,15 +27,18 @@ internal class ChocolateyPackageInformationService : IChocolateyPackageInformati { private readonly IFileSystem _fileSystem; private readonly IRegistryService _registryService; + private readonly IFilesService _filesService; private const string REGISTRY_SNAPSHOT_FILE = ".registry"; + private const string FILES_SNAPSHOT_FILE = ".files"; private const string SILENT_UNINSTALLER_FILE = ".silentUninstaller"; private const string SIDE_BY_SIDE_FILE = ".sxs"; private const string PIN_FILE = ".pin"; - public ChocolateyPackageInformationService(IFileSystem fileSystem, IRegistryService registryService) + public ChocolateyPackageInformationService(IFileSystem fileSystem, IRegistryService registryService, IFilesService filesService) { _fileSystem = fileSystem; _registryService = registryService; + _filesService = filesService; } public ChocolateyPackageInformation get_package_information(IPackage package) @@ -63,6 +66,16 @@ public ChocolateyPackageInformation get_package_information(IPackage package) logWarningInsteadOfError: true ); + FaultTolerance.try_catch_with_logging_exception( + () => + { + packageInformation.FilesSnapshot = _filesService.read_from_file(_fileSystem.combine_paths(pkgStorePath, FILES_SNAPSHOT_FILE)); + }, + "Unable to read files snapshot file", + throwError: false, + logWarningInsteadOfError: true + ); + packageInformation.HasSilentUninstall = _fileSystem.file_exists(_fileSystem.combine_paths(pkgStorePath, SILENT_UNINSTALLER_FILE)); packageInformation.IsSideBySide = _fileSystem.file_exists(_fileSystem.combine_paths(pkgStorePath, SIDE_BY_SIDE_FILE)); packageInformation.IsPinned = _fileSystem.file_exists(_fileSystem.combine_paths(pkgStorePath, PIN_FILE)); @@ -89,6 +102,11 @@ public void save_package_information(ChocolateyPackageInformation packageInforma _registryService.save_to_file(packageInformation.RegistrySnapshot, _fileSystem.combine_paths(pkgStorePath, REGISTRY_SNAPSHOT_FILE)); } + if (packageInformation.FilesSnapshot != null) + { + _filesService.save_to_file(packageInformation.FilesSnapshot, _fileSystem.combine_paths(pkgStorePath, FILES_SNAPSHOT_FILE)); + } + if (packageInformation.HasSilentUninstall) { _fileSystem.write_file(_fileSystem.combine_paths(pkgStorePath, SILENT_UNINSTALLER_FILE), string.Empty, Encoding.ASCII); From 2da7306cd6ae65928c69a8e0b85f09d2368e234d Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Sun, 3 May 2015 15:39:33 -0500 Subject: [PATCH 10/27] (GH-121) IFileSystem.read_file_bytes Allow IFileSystem to read a file and return the file bytes as an array. --- .../infrastructure/filesystem/DotNetFileSystem.cs | 5 +++++ src/chocolatey/infrastructure/filesystem/IFileSystem.cs | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/src/chocolatey/infrastructure/filesystem/DotNetFileSystem.cs b/src/chocolatey/infrastructure/filesystem/DotNetFileSystem.cs index 41cde09571..f2dc91eaec 100644 --- a/src/chocolatey/infrastructure/filesystem/DotNetFileSystem.cs +++ b/src/chocolatey/infrastructure/filesystem/DotNetFileSystem.cs @@ -231,6 +231,11 @@ public string read_file(string filePath) return File.ReadAllText(filePath, get_file_encoding(filePath)); } + public byte[] read_file_bytes(string filePath) + { + return File.ReadAllBytes(filePath); + } + public FileStream open_file_readonly(string filePath) { return File.OpenRead(filePath); diff --git a/src/chocolatey/infrastructure/filesystem/IFileSystem.cs b/src/chocolatey/infrastructure/filesystem/IFileSystem.cs index 1595959418..959a9205c9 100644 --- a/src/chocolatey/infrastructure/filesystem/IFileSystem.cs +++ b/src/chocolatey/infrastructure/filesystem/IFileSystem.cs @@ -196,6 +196,13 @@ public interface IFileSystem /// A string of the file contents string read_file(string filePath); + /// + /// Returns the contents of a file as bytes. + /// + /// The filepath. + /// A byte array of the file contents + byte[] read_file_bytes(string filePath); + /// /// Opens a file /// @@ -330,5 +337,6 @@ public interface IFileSystem /// The path. /// The attributes. void ensure_file_attribute_set(string path, FileAttributes attributes); + } } \ No newline at end of file From 31d23f226d073d142b10e290ac8ec68e872d7b4e Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Sun, 3 May 2015 15:43:12 -0500 Subject: [PATCH 11/27] (GH-121) Add hash provider To compute hashes in files, add a hash provider that can determine hashes of files. --- src/chocolatey/chocolatey.csproj | 2 + .../registration/ContainerBinding.cs | 3 ++ .../cryptography/IHashProvider.cs | 30 +++++++++++++ .../cryptography/Md5HashProvider.cs | 42 +++++++++++++++++++ 4 files changed, 77 insertions(+) create mode 100644 src/chocolatey/infrastructure/cryptography/IHashProvider.cs create mode 100644 src/chocolatey/infrastructure/cryptography/Md5HashProvider.cs diff --git a/src/chocolatey/chocolatey.csproj b/src/chocolatey/chocolatey.csproj index 18b9ede0fa..fcb6693aec 100644 --- a/src/chocolatey/chocolatey.csproj +++ b/src/chocolatey/chocolatey.csproj @@ -166,6 +166,8 @@ + + diff --git a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs index 28fde79390..762607914c 100644 --- a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs +++ b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs @@ -20,6 +20,7 @@ namespace chocolatey.infrastructure.app.registration using SimpleInjector; using adapters; using commands; + using cryptography; using events; using filesystem; using infrastructure.commands; @@ -28,6 +29,7 @@ namespace chocolatey.infrastructure.app.registration using nuget; using services; using IFileSystem = filesystem.IFileSystem; + using IHashProvider = cryptography.IHashProvider; // ReSharper disable InconsistentNaming @@ -54,6 +56,7 @@ public void RegisterComponents(Container container) container.Register(Lifestyle.Singleton); container.Register(Lifestyle.Singleton); container.Register(Lifestyle.Singleton); + container.Register(Lifestyle.Singleton); container.Register(Lifestyle.Singleton); container.Register(Lifestyle.Singleton); container.Register(Lifestyle.Singleton); diff --git a/src/chocolatey/infrastructure/cryptography/IHashProvider.cs b/src/chocolatey/infrastructure/cryptography/IHashProvider.cs new file mode 100644 index 0000000000..5356a05097 --- /dev/null +++ b/src/chocolatey/infrastructure/cryptography/IHashProvider.cs @@ -0,0 +1,30 @@ +// Copyright © 2011 - Present RealDimensions Software, LLC +// +// 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.cryptography +{ + /// + /// A hash provider for hashing files + /// + public interface IHashProvider + { + /// + /// Returns a hash of the specified file path. + /// + /// The file path. + /// A computed hash of the file, based on the contents. + string hash_file(string filePath); + } +} \ No newline at end of file diff --git a/src/chocolatey/infrastructure/cryptography/Md5HashProvider.cs b/src/chocolatey/infrastructure/cryptography/Md5HashProvider.cs new file mode 100644 index 0000000000..e15004bc57 --- /dev/null +++ b/src/chocolatey/infrastructure/cryptography/Md5HashProvider.cs @@ -0,0 +1,42 @@ +// Copyright © 2011 - Present RealDimensions Software, LLC +// +// 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.cryptography +{ + using System; + using System.Security.Cryptography; + using filesystem; + + public sealed class Md5HashProvider : IHashProvider + { + private readonly IFileSystem _fileSystem; + private readonly MD5 _cryptoProvider; + + public Md5HashProvider(IFileSystem fileSystem) + { + _fileSystem = fileSystem; + _cryptoProvider = MD5.Create(); + } + + public string hash_file(string filePath) + { + if (!_fileSystem.file_exists(filePath)) return string.Empty; + + var hash = _cryptoProvider.ComputeHash(_fileSystem.read_file_bytes(filePath)); + + return BitConverter.ToString(hash).Replace("-", string.Empty); + } + } +} \ No newline at end of file From e27450acd474fca2362d52b6ac3613aae14dbc78 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Sun, 3 May 2015 16:08:28 -0500 Subject: [PATCH 12/27] (GH-121) specs for Md5HashProvider --- .../chocolatey.tests.integration.csproj | 1 + .../cryptography/Md5HashProviderSpecs.cs | 67 +++++++++++++++++++ src/chocolatey.tests/chocolatey.tests.csproj | 1 + .../cryptography/Md5HashProviderSpecs.cs | 65 ++++++++++++++++++ 4 files changed, 134 insertions(+) create mode 100644 src/chocolatey.tests.integration/infrastructure/cryptography/Md5HashProviderSpecs.cs create mode 100644 src/chocolatey.tests/infrastructure/cryptography/Md5HashProviderSpecs.cs diff --git a/src/chocolatey.tests.integration/chocolatey.tests.integration.csproj b/src/chocolatey.tests.integration/chocolatey.tests.integration.csproj index 52fd7c9a05..36d7ca3485 100644 --- a/src/chocolatey.tests.integration/chocolatey.tests.integration.csproj +++ b/src/chocolatey.tests.integration/chocolatey.tests.integration.csproj @@ -76,6 +76,7 @@ + diff --git a/src/chocolatey.tests.integration/infrastructure/cryptography/Md5HashProviderSpecs.cs b/src/chocolatey.tests.integration/infrastructure/cryptography/Md5HashProviderSpecs.cs new file mode 100644 index 0000000000..ca63b1c570 --- /dev/null +++ b/src/chocolatey.tests.integration/infrastructure/cryptography/Md5HashProviderSpecs.cs @@ -0,0 +1,67 @@ +// Copyright © 2011 - Present RealDimensions Software, LLC +// +// 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.tests.integration.infrastructure.cryptography +{ + using System; + using System.IO; + using System.Reflection; + using System.Security.Cryptography; + using Should; + using chocolatey.infrastructure.cryptography; + using chocolatey.infrastructure.filesystem; + + public class Md5HashProviderSpecs + { + public abstract class Md5HashProviderSpecsBase : TinySpec + { + protected Md5HashProvider Provider; + protected DotNetFileSystem FileSystem; + protected string ContextDirectory; + + public override void Context() + { + FileSystem = new DotNetFileSystem(); + Provider = new Md5HashProvider(FileSystem); + ContextDirectory = FileSystem.combine_paths(FileSystem.get_directory_name(Assembly.GetExecutingAssembly().CodeBase.Replace("file:///", string.Empty)), "context"); + } + } + + public class when_Md5HashProvider_provides_a_hash : Md5HashProviderSpecsBase + { + private string result; + private string filePath; + + public override void Context() + { + base.Context(); + filePath = FileSystem.combine_paths(ContextDirectory, "testing.packages.config"); + } + + public override void Because() + { + result = Provider.hash_file(filePath); + } + + [Fact] + public void should_provide_the_correct_hash_based_on_a_checksum() + { + var expected = BitConverter.ToString(MD5.Create().ComputeHash(File.ReadAllBytes(filePath))).Replace("-", string.Empty); + + result.ShouldEqual(expected); + } + } + } +} \ No newline at end of file diff --git a/src/chocolatey.tests/chocolatey.tests.csproj b/src/chocolatey.tests/chocolatey.tests.csproj index f31a59eecf..5693af63ce 100644 --- a/src/chocolatey.tests/chocolatey.tests.csproj +++ b/src/chocolatey.tests/chocolatey.tests.csproj @@ -82,6 +82,7 @@ + diff --git a/src/chocolatey.tests/infrastructure/cryptography/Md5HashProviderSpecs.cs b/src/chocolatey.tests/infrastructure/cryptography/Md5HashProviderSpecs.cs new file mode 100644 index 0000000000..560d47255f --- /dev/null +++ b/src/chocolatey.tests/infrastructure/cryptography/Md5HashProviderSpecs.cs @@ -0,0 +1,65 @@ +// Copyright © 2011 - Present RealDimensions Software, LLC +// +// 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.tests.infrastructure.cryptography +{ + using System; + using System.Security.Cryptography; + using Moq; + using Should; + using chocolatey.infrastructure.cryptography; + using chocolatey.infrastructure.filesystem; + + public class Md5HashProviderSpecs + { + public abstract class Md5HashProviderSpecsBase : TinySpec + { + protected Md5HashProvider Provider; + protected Mock FileSystem = new Mock(); + + public override void Context() + { + Provider = new Md5HashProvider(FileSystem.Object); + } + } + + public class when_Md5HashProvider_provides_a_hash : Md5HashProviderSpecsBase + { + private string result; + private string filePath = "c:\\path\\does\\not\\matter.txt"; + private readonly byte[] byteArray = new byte[] {23, 25, 27}; + + public override void Context() + { + base.Context(); + FileSystem.Setup(x => x.file_exists(It.IsAny())).Returns(true); + FileSystem.Setup(x => x.read_file_bytes(filePath)).Returns(byteArray); + } + + public override void Because() + { + result = Provider.hash_file(filePath); + } + + [Fact] + public void should_provide_the_correct_hash_based_on_a_checksum() + { + var expected = BitConverter.ToString(MD5.Create().ComputeHash(byteArray)).Replace("-", string.Empty); + + result.ShouldEqual(expected); + } + } + } +} \ No newline at end of file From a52adca01b78d723b5f01f11d1be2d5b63a2b2e6 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Mon, 4 May 2015 08:44:11 -0500 Subject: [PATCH 13/27] (GH-121) Capture file hashes Capture the package hashes after everything, including the install script, has had a chance to run. --- .../services/ChocolateyPackageService.cs | 9 +++++- .../services/FilesService.cs | 32 ++++++++++++++++++- .../services/IFilesService.cs | 24 ++++++++++++++ 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs b/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs index 603ddcf0bc..aa8db5f3ba 100644 --- a/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs +++ b/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs @@ -37,10 +37,14 @@ public class ChocolateyPackageService : IChocolateyPackageService private readonly IFileSystem _fileSystem; private readonly IRegistryService _registryService; private readonly IChocolateyPackageInformationService _packageInfoService; + private readonly IFilesService _filesService; private readonly IAutomaticUninstallerService _autoUninstallerService; private readonly IXmlService _xmlService; - public ChocolateyPackageService(INugetService nugetService, IPowershellService powershellService, IShimGenerationService shimgenService, IFileSystem fileSystem, IRegistryService registryService, IChocolateyPackageInformationService packageInfoService, IAutomaticUninstallerService autoUninstallerService, IXmlService xmlService) + public ChocolateyPackageService(INugetService nugetService, IPowershellService powershellService, + IShimGenerationService shimgenService, IFileSystem fileSystem, IRegistryService registryService, + IChocolateyPackageInformationService packageInfoService, IFilesService filesService, + IAutomaticUninstallerService autoUninstallerService, IXmlService xmlService) { _nugetService = nugetService; _powershellService = powershellService; @@ -48,6 +52,7 @@ public ChocolateyPackageService(INugetService nugetService, IPowershellService p _fileSystem = fileSystem; _registryService = registryService; _packageInfoService = packageInfoService; + _filesService = filesService; _autoUninstallerService = autoUninstallerService; _xmlService = xmlService; } @@ -186,6 +191,8 @@ public void handle_package_result(PackageResult packageResult, ChocolateyConfigu } } + pkgInfo.FilesSnapshot = _filesService.capture_package_files(packageResult, config); + if (packageResult.Success) { _shimgenService.install(config, packageResult); diff --git a/src/chocolatey/infrastructure.app/services/FilesService.cs b/src/chocolatey/infrastructure.app/services/FilesService.cs index c8e0adee6d..fff059df6d 100644 --- a/src/chocolatey/infrastructure.app/services/FilesService.cs +++ b/src/chocolatey/infrastructure.app/services/FilesService.cs @@ -15,19 +15,26 @@ namespace chocolatey.infrastructure.app.services { + using System; + using System.IO; + using configuration; + using cryptography; using domain; using filesystem; using infrastructure.services; + using results; public sealed class FilesService : IFilesService { private readonly IXmlService _xmlService; private readonly IFileSystem _fileSystem; + private readonly IHashProvider _hashProvider; - public FilesService(IXmlService xmlService, IFileSystem fileSystem) + public FilesService(IXmlService xmlService, IFileSystem fileSystem, IHashProvider hashProvider) { _xmlService = xmlService; _fileSystem = fileSystem; + _hashProvider = hashProvider; } public PackageFiles read_from_file(string filePath) @@ -44,5 +51,28 @@ public void save_to_file(PackageFiles snapshot, string filePath) { _xmlService.serialize(snapshot, filePath); } + + public PackageFiles capture_package_files(PackageResult packageResult, ChocolateyConfiguration config) + { + var installDirectory = packageResult.InstallLocation; + if (installDirectory.is_equal_to(ApplicationParameters.InstallLocation) || installDirectory.is_equal_to(ApplicationParameters.PackagesLocation)) + { + var logMessage = "Install location is not specific enough, cannot capture files:{0} Erroneous install location captured as '{1}'".format_with(Environment.NewLine, packageResult.InstallLocation); + packageResult.Messages.Add(new ResultMessage(ResultType.Warn, logMessage)); + this.Log().Error(logMessage); + return null; + } + + var packageFiles = new PackageFiles(); + + //gather all files in the folder + var files = _fileSystem.get_files(installDirectory, pattern: "*.*", option: SearchOption.AllDirectories); + foreach (string file in files.or_empty_list_if_null()) + { + packageFiles.Files.Add(new PackageFile { Path = file, Checksum = _hashProvider.hash_file(file)}); + } + + return packageFiles; + } } } \ No newline at end of file diff --git a/src/chocolatey/infrastructure.app/services/IFilesService.cs b/src/chocolatey/infrastructure.app/services/IFilesService.cs index daf42c7fcf..7900ac4735 100644 --- a/src/chocolatey/infrastructure.app/services/IFilesService.cs +++ b/src/chocolatey/infrastructure.app/services/IFilesService.cs @@ -15,11 +15,35 @@ namespace chocolatey.infrastructure.app.services { + using configuration; using domain; + using results; + /// + /// The files service for capturing and handling file snapshots. + /// public interface IFilesService { + /// + /// Read the package files file from the specified filepath. + /// + /// The filepath. + /// PackageFiles with entries based on the file if it exists, otherwise null PackageFiles read_from_file(string filepath); + + /// + /// Saves the files snapshot to the specifed file path. + /// + /// The snapshot. + /// The file path. void save_to_file(PackageFiles snapshot, string filePath); + + /// + /// Captures the snapshot of the package files + /// + /// The package result. + /// The configuration. + /// PackageFiles with entries based on the install location of the package. + PackageFiles capture_package_files(PackageResult packageResult, ChocolateyConfiguration config); } } \ No newline at end of file From 8b22eeb6abfa9dedf05afb9a9ae6f77c486eaaad Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Mon, 4 May 2015 10:29:25 -0500 Subject: [PATCH 14/27] (GH-121) Specs and Files Service hardening - Rename Md5HashProvider to CryptoHashProvider and provide enumeration for HashAlgorithm Crytpo providers. - Implement adapter for HashAlgorithm. - Update specs for CryptoHashProvider - Specs for FilesService --- .../chocolatey.tests.integration.csproj | 2 +- ...derSpecs.cs => CrytpoHashProviderSpecs.cs} | 10 +- src/chocolatey.tests/chocolatey.tests.csproj | 3 +- .../services/FilesServiceSpecs.cs | 248 ++++++++++++++++++ .../cryptography/CrytpoHashProvider.cs | 109 ++++++++ .../cryptography/Md5HashProviderSpecs.cs | 65 ----- src/chocolatey/chocolatey.csproj | 5 +- .../ApplicationParameters.cs | 2 + .../registration/ContainerBinding.cs | 2 +- .../services/FilesService.cs | 9 +- .../infrastructure/adapters/HashAlgorithm.cs | 19 ++ .../infrastructure/adapters/IHashAlgorithm.cs | 11 + .../cryptography/CryptoHashProviderType.cs | 10 + .../cryptography/CrytpoHashProvider.cs | 79 ++++++ .../cryptography/Md5HashProvider.cs | 42 --- 15 files changed, 499 insertions(+), 117 deletions(-) rename src/chocolatey.tests.integration/infrastructure/cryptography/{Md5HashProviderSpecs.cs => CrytpoHashProviderSpecs.cs} (84%) create mode 100644 src/chocolatey.tests/infrastructure.app/services/FilesServiceSpecs.cs create mode 100644 src/chocolatey.tests/infrastructure/cryptography/CrytpoHashProvider.cs delete mode 100644 src/chocolatey.tests/infrastructure/cryptography/Md5HashProviderSpecs.cs create mode 100644 src/chocolatey/infrastructure/adapters/HashAlgorithm.cs create mode 100644 src/chocolatey/infrastructure/adapters/IHashAlgorithm.cs create mode 100644 src/chocolatey/infrastructure/cryptography/CryptoHashProviderType.cs create mode 100644 src/chocolatey/infrastructure/cryptography/CrytpoHashProvider.cs delete mode 100644 src/chocolatey/infrastructure/cryptography/Md5HashProvider.cs diff --git a/src/chocolatey.tests.integration/chocolatey.tests.integration.csproj b/src/chocolatey.tests.integration/chocolatey.tests.integration.csproj index 36d7ca3485..74e6aa82d5 100644 --- a/src/chocolatey.tests.integration/chocolatey.tests.integration.csproj +++ b/src/chocolatey.tests.integration/chocolatey.tests.integration.csproj @@ -76,7 +76,7 @@ - + diff --git a/src/chocolatey.tests.integration/infrastructure/cryptography/Md5HashProviderSpecs.cs b/src/chocolatey.tests.integration/infrastructure/cryptography/CrytpoHashProviderSpecs.cs similarity index 84% rename from src/chocolatey.tests.integration/infrastructure/cryptography/Md5HashProviderSpecs.cs rename to src/chocolatey.tests.integration/infrastructure/cryptography/CrytpoHashProviderSpecs.cs index ca63b1c570..20aa0947e7 100644 --- a/src/chocolatey.tests.integration/infrastructure/cryptography/Md5HashProviderSpecs.cs +++ b/src/chocolatey.tests.integration/infrastructure/cryptography/CrytpoHashProviderSpecs.cs @@ -23,23 +23,23 @@ namespace chocolatey.tests.integration.infrastructure.cryptography using chocolatey.infrastructure.cryptography; using chocolatey.infrastructure.filesystem; - public class Md5HashProviderSpecs + public class CrytpoHashProviderSpecs { - public abstract class Md5HashProviderSpecsBase : TinySpec + public abstract class CrytpoHashProviderSpecsBase : TinySpec { - protected Md5HashProvider Provider; + protected CrytpoHashProvider Provider; protected DotNetFileSystem FileSystem; protected string ContextDirectory; public override void Context() { FileSystem = new DotNetFileSystem(); - Provider = new Md5HashProvider(FileSystem); + Provider = new CrytpoHashProvider(FileSystem,CryptoHashProviderType.Md5); ContextDirectory = FileSystem.combine_paths(FileSystem.get_directory_name(Assembly.GetExecutingAssembly().CodeBase.Replace("file:///", string.Empty)), "context"); } } - public class when_Md5HashProvider_provides_a_hash : Md5HashProviderSpecsBase + public class when_HashProvider_provides_a_hash : CrytpoHashProviderSpecsBase { private string result; private string filePath; diff --git a/src/chocolatey.tests/chocolatey.tests.csproj b/src/chocolatey.tests/chocolatey.tests.csproj index 5693af63ce..411590c1d4 100644 --- a/src/chocolatey.tests/chocolatey.tests.csproj +++ b/src/chocolatey.tests/chocolatey.tests.csproj @@ -77,12 +77,13 @@ + - + diff --git a/src/chocolatey.tests/infrastructure.app/services/FilesServiceSpecs.cs b/src/chocolatey.tests/infrastructure.app/services/FilesServiceSpecs.cs new file mode 100644 index 0000000000..dd8bf96c41 --- /dev/null +++ b/src/chocolatey.tests/infrastructure.app/services/FilesServiceSpecs.cs @@ -0,0 +1,248 @@ +namespace chocolatey.tests.infrastructure.app.services +{ + using System; + using System.Collections.Generic; + using System.IO; + using Moq; + using Should; + using chocolatey.infrastructure.app; + using chocolatey.infrastructure.app.configuration; + using chocolatey.infrastructure.app.domain; + using chocolatey.infrastructure.app.services; + using chocolatey.infrastructure.cryptography; + using chocolatey.infrastructure.filesystem; + using chocolatey.infrastructure.results; + using chocolatey.infrastructure.services; + + public class FilesServiceSpecs + { + public abstract class FilesServiceSpecsBase : TinySpec + { + protected FilesService Service; + protected Mock XmlService = new Mock(); + protected Mock FileSystem = new Mock(); + protected Mock HashProvider = new Mock(); + + public override void Context() + { + XmlService.ResetCalls(); + FileSystem.ResetCalls(); + HashProvider.ResetCalls(); + Service = new FilesService(XmlService.Object,FileSystem.Object,HashProvider.Object); + } + } + + public class when_FilesService_reads_from_files : FilesServiceSpecsBase + { + private Func because; + + public override void Because() + { + because = () => Service.read_from_file("fake path"); + } + + [Fact] + public void should_deserialize_when_file_exists() + { + Context(); + FileSystem.Setup(x => x.file_exists(It.IsAny())).Returns(true); + XmlService.Setup(x => x.deserialize(It.IsAny())).Returns(new PackageFiles()); + + because(); + } + + [Fact] + public void should_not_deserialize_if_file_does_not_exist() + { + Context(); + FileSystem.Setup(x => x.file_exists(It.IsAny())).Returns(false); + + because(); + + XmlService.Verify(x => x.deserialize(It.IsAny()),Times.Never); + } + } + + public class when_FilesService_saves_files : FilesServiceSpecsBase + { + private Action because; + private PackageFiles files; + + public override void Because() + { + because = () => Service.save_to_file(files , "fake path"); + } + + [Fact] + public void should_save_if_the_snapshot_is_not_null() + { + Context(); + files = new PackageFiles(); + + because(); + + XmlService.Verify(x => x.serialize(files, It.IsAny()), Times.Once()); + } + + [Fact] + public void should_not_do_anything_if_the_snapshot_is_null() + { + Context(); + files = null; + + because(); + + XmlService.Verify(x => x.serialize(files, It.IsAny()), Times.Never); + } + } + + public class when_FilesService_captures_files_and_install_directory_reports_choco_install_location : FilesServiceSpecsBase + { + private PackageFiles result; + private PackageResult packageResult; + private readonly ChocolateyConfiguration config = new ChocolateyConfiguration(); + + public override void Context() + { + base.Context(); + packageResult = new PackageResult("bob", "1.2.3", ApplicationParameters.InstallLocation); + } + + public override void Because() + { + result = Service.capture_package_files(packageResult, config); + } + + [Fact] + public void should_not_call_get_files() + { + FileSystem.Verify(x => x.get_files(It.IsAny(), It.IsAny(), SearchOption.AllDirectories), Times.Never); + } + + [Fact] + public void should_return_a_warning_if_the_install_directory_matches_choco_install_location() + { + packageResult.Warning.ShouldBeTrue(); + } + + [Fact] + public void should_return_null() + { + result.ShouldBeNull(); + } + } + + public class when_FilesService_captures_files_and_install_directory_reports_packages_location : FilesServiceSpecsBase + { + private PackageFiles result; + private PackageResult packageResult; + private readonly ChocolateyConfiguration config = new ChocolateyConfiguration(); + + public override void Context() + { + base.Context(); + packageResult = new PackageResult("bob", "1.2.3", ApplicationParameters.PackagesLocation); + } + + public override void Because() + { + result = Service.capture_package_files(packageResult, config); + } + + [Fact] + public void should_not_call_get_files() + { + FileSystem.Verify(x => x.get_files(It.IsAny(), It.IsAny(), SearchOption.AllDirectories), Times.Never); + } + + [Fact] + public void should_return_a_warning_if_the_install_directory_matches_choco_install_location() + { + packageResult.Warning.ShouldBeTrue(); + } + + [Fact] + public void should_return_null() + { + result.ShouldBeNull(); + } + } + + public class when_FilesService_captures_files_and_package_result_is_null : FilesServiceSpecsBase + { + private PackageFiles result; + private PackageResult packageResult; + private readonly ChocolateyConfiguration config = new ChocolateyConfiguration(); + + public override void Context() + { + base.Context(); + packageResult = null; + } + + public override void Because() + { + result = Service.capture_package_files(packageResult, config); + } + + [Fact] + public void should_not_call_get_files() + { + FileSystem.Verify(x => x.get_files(It.IsAny(), It.IsAny(), SearchOption.AllDirectories), Times.Never); + } + + [Fact] + public void should_return_null() + { + result.ShouldBeNull(); + } + } + + public class when_FilesService_captures_files_happy_path : FilesServiceSpecsBase + { + private PackageFiles result; + private PackageResult packageResult; + private readonly ChocolateyConfiguration config = new ChocolateyConfiguration(); + private readonly string installDirectory = ApplicationParameters.PackagesLocation + "\\bob"; + private readonly IList files = new List { "file1", "file2" }; + + public override void Context() + { + base.Context(); + packageResult = new PackageResult("bob", "1.2.3", installDirectory); + + FileSystem.Setup(x => x.get_files(ApplicationParameters.PackagesLocation + "\\bob", It.IsAny(), SearchOption.AllDirectories)).Returns(files); + HashProvider.Setup(x => x.hash_file(It.IsAny())).Returns("yes"); + } + + public override void Because() + { + result = Service.capture_package_files(packageResult, config); + } + + [Fact] + public void should_return_a_PackageFiles_object() + { + result.ShouldNotBeNull(); + } + + [Fact] + public void should_contain_package_files() + { + result.Files.ShouldNotBeEmpty(); + } + + [Fact] + public void should_contain_the_correct_number_of_package_files() + { + result.Files.Count.ShouldEqual(files.Count); + } + + [Fact] + public void should_call_hash_provider_for_each_file() + { + HashProvider.Verify(x => x.hash_file(It.IsAny()),Times.Exactly(files.Count)); + } + } + } +} \ No newline at end of file diff --git a/src/chocolatey.tests/infrastructure/cryptography/CrytpoHashProvider.cs b/src/chocolatey.tests/infrastructure/cryptography/CrytpoHashProvider.cs new file mode 100644 index 0000000000..4222e4f970 --- /dev/null +++ b/src/chocolatey.tests/infrastructure/cryptography/CrytpoHashProvider.cs @@ -0,0 +1,109 @@ +// Copyright © 2011 - Present RealDimensions Software, LLC +// +// 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.tests.infrastructure.cryptography +{ + using System; + using System.IO; + using System.Security.Cryptography; + using Moq; + using Should; + using chocolatey.infrastructure.adapters; + using chocolatey.infrastructure.app; + using chocolatey.infrastructure.cryptography; + using chocolatey.infrastructure.filesystem; + + public class CrytpoHashProviderSpecs + { + public abstract class CrytpoHashProviderSpecsBase : TinySpec + { + protected CrytpoHashProvider Provider; + protected Mock FileSystem = new Mock(); + + public override void Context() + { + Provider = new CrytpoHashProvider(FileSystem.Object, CryptoHashProviderType.Md5); + } + } + + public class when_HashProvider_provides_a_hash : CrytpoHashProviderSpecsBase + { + private string result; + private string filePath = "c:\\path\\does\\not\\matter.txt"; + private readonly byte[] byteArray = new byte[] {23, 25, 27}; + + public override void Context() + { + base.Context(); + FileSystem.Setup(x => x.file_exists(It.IsAny())).Returns(true); + FileSystem.Setup(x => x.read_file_bytes(filePath)).Returns(byteArray); + } + + public override void Because() + { + result = Provider.hash_file(filePath); + } + + [Fact] + public void should_provide_the_correct_hash_based_on_a_checksum() + { + var expected = BitConverter.ToString(MD5.Create().ComputeHash(byteArray)).Replace("-", string.Empty); + + result.ShouldEqual(expected); + } + } + + public class when_HashProvider_attempts_to_provide_a_hash_for_a_file_over_2GB : CrytpoHashProviderSpecsBase + { + private string result; + private string filePath = "c:\\path\\does\\not\\matter.txt"; + private readonly byte[] byteArray = new byte[] { 23, 25, 27 }; + private readonly Mock _hashAlgorithm = new Mock(); + + public override void Context() + { + base.Context(); + Provider = new CrytpoHashProvider(FileSystem.Object, _hashAlgorithm.Object); + + FileSystem.Setup(x => x.file_exists(It.IsAny())).Returns(true); + FileSystem.Setup(x => x.read_file_bytes(filePath)).Returns(byteArray); + _hashAlgorithm.Setup(x => x.ComputeHash(byteArray)).Throws(); //IO.IO_FileTooLong2GB (over Int32.MaxValue) + } + + public override void Because() + { + result = Provider.hash_file(filePath); + } + + [Fact] + public void should_log_a_warning() + { + MockLogger.MessagesFor(LogLevel.Warn).Count.ShouldEqual(1); + } + + [Fact] + public void should_not_throw_an_error_itself() + { + //this handles itself + } + + [Fact] + public void should_provide_an_unchanging_hash_for_a_file_too_big_to_hash() + { + result.ShouldEqual(ApplicationParameters.HashProviderFileTooBig); + } + } + } +} \ No newline at end of file diff --git a/src/chocolatey.tests/infrastructure/cryptography/Md5HashProviderSpecs.cs b/src/chocolatey.tests/infrastructure/cryptography/Md5HashProviderSpecs.cs deleted file mode 100644 index 560d47255f..0000000000 --- a/src/chocolatey.tests/infrastructure/cryptography/Md5HashProviderSpecs.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright © 2011 - Present RealDimensions Software, LLC -// -// 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.tests.infrastructure.cryptography -{ - using System; - using System.Security.Cryptography; - using Moq; - using Should; - using chocolatey.infrastructure.cryptography; - using chocolatey.infrastructure.filesystem; - - public class Md5HashProviderSpecs - { - public abstract class Md5HashProviderSpecsBase : TinySpec - { - protected Md5HashProvider Provider; - protected Mock FileSystem = new Mock(); - - public override void Context() - { - Provider = new Md5HashProvider(FileSystem.Object); - } - } - - public class when_Md5HashProvider_provides_a_hash : Md5HashProviderSpecsBase - { - private string result; - private string filePath = "c:\\path\\does\\not\\matter.txt"; - private readonly byte[] byteArray = new byte[] {23, 25, 27}; - - public override void Context() - { - base.Context(); - FileSystem.Setup(x => x.file_exists(It.IsAny())).Returns(true); - FileSystem.Setup(x => x.read_file_bytes(filePath)).Returns(byteArray); - } - - public override void Because() - { - result = Provider.hash_file(filePath); - } - - [Fact] - public void should_provide_the_correct_hash_based_on_a_checksum() - { - var expected = BitConverter.ToString(MD5.Create().ComputeHash(byteArray)).Replace("-", string.Empty); - - result.ShouldEqual(expected); - } - } - } -} \ No newline at end of file diff --git a/src/chocolatey/chocolatey.csproj b/src/chocolatey/chocolatey.csproj index fcb6693aec..f75bdca93a 100644 --- a/src/chocolatey/chocolatey.csproj +++ b/src/chocolatey/chocolatey.csproj @@ -166,8 +166,11 @@ + + + - + diff --git a/src/chocolatey/infrastructure.app/ApplicationParameters.cs b/src/chocolatey/infrastructure.app/ApplicationParameters.cs index a55cf8bbf7..f2a95be4d3 100644 --- a/src/chocolatey/infrastructure.app/ApplicationParameters.cs +++ b/src/chocolatey/infrastructure.app/ApplicationParameters.cs @@ -63,6 +63,8 @@ public static class ApplicationParameters public static int DefaultWaitForExitInSeconds = 2700; public static readonly string[] ConfigFileExtensions = new string[] {".autoconf",".config",".conf",".cfg",".jsc",".json",".jsonp",".ini",".xml",".yaml"}; + + public static string HashProviderFileTooBig = "UnableToDetectChanges_FileTooBig"; public static class Tools { diff --git a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs index 762607914c..794b3463b5 100644 --- a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs +++ b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs @@ -56,7 +56,7 @@ public void RegisterComponents(Container container) container.Register(Lifestyle.Singleton); container.Register(Lifestyle.Singleton); container.Register(Lifestyle.Singleton); - container.Register(Lifestyle.Singleton); + container.Register(() => new CrytpoHashProvider(container.GetInstance(), CryptoHashProviderType.Md5), Lifestyle.Singleton); container.Register(Lifestyle.Singleton); container.Register(Lifestyle.Singleton); container.Register(Lifestyle.Singleton); diff --git a/src/chocolatey/infrastructure.app/services/FilesService.cs b/src/chocolatey/infrastructure.app/services/FilesService.cs index fff059df6d..2b87891b65 100644 --- a/src/chocolatey/infrastructure.app/services/FilesService.cs +++ b/src/chocolatey/infrastructure.app/services/FilesService.cs @@ -49,11 +49,15 @@ public PackageFiles read_from_file(string filePath) public void save_to_file(PackageFiles snapshot, string filePath) { + if (snapshot == null) return; + _xmlService.serialize(snapshot, filePath); } public PackageFiles capture_package_files(PackageResult packageResult, ChocolateyConfiguration config) { + if (packageResult == null) return null; + var installDirectory = packageResult.InstallLocation; if (installDirectory.is_equal_to(ApplicationParameters.InstallLocation) || installDirectory.is_equal_to(ApplicationParameters.PackagesLocation)) { @@ -65,11 +69,14 @@ public PackageFiles capture_package_files(PackageResult packageResult, Chocolate var packageFiles = new PackageFiles(); + this.Log().Debug(() => "Capturing package files in '{0}'".format_with(installDirectory)); //gather all files in the folder var files = _fileSystem.get_files(installDirectory, pattern: "*.*", option: SearchOption.AllDirectories); foreach (string file in files.or_empty_list_if_null()) { - packageFiles.Files.Add(new PackageFile { Path = file, Checksum = _hashProvider.hash_file(file)}); + var hash = _hashProvider.hash_file(file); + this.Log().Debug(() => " Found '{0}'{1} with checksum '{2}'".format_with(file, Environment.NewLine, hash)); + packageFiles.Files.Add(new PackageFile { Path = file, Checksum = hash}); } return packageFiles; diff --git a/src/chocolatey/infrastructure/adapters/HashAlgorithm.cs b/src/chocolatey/infrastructure/adapters/HashAlgorithm.cs new file mode 100644 index 0000000000..1c303a1cec --- /dev/null +++ b/src/chocolatey/infrastructure/adapters/HashAlgorithm.cs @@ -0,0 +1,19 @@ +namespace chocolatey.infrastructure.adapters +{ + using cryptography; + + public sealed class HashAlgorithm : IHashAlgorithm + { + private readonly System.Security.Cryptography.HashAlgorithm _algorithm; + + public HashAlgorithm(System.Security.Cryptography.HashAlgorithm algorithm) + { + _algorithm = algorithm; + } + + public byte[] ComputeHash(byte[] buffer) + { + return _algorithm.ComputeHash(buffer); + } + } +} \ No newline at end of file diff --git a/src/chocolatey/infrastructure/adapters/IHashAlgorithm.cs b/src/chocolatey/infrastructure/adapters/IHashAlgorithm.cs new file mode 100644 index 0000000000..5d87e8797e --- /dev/null +++ b/src/chocolatey/infrastructure/adapters/IHashAlgorithm.cs @@ -0,0 +1,11 @@ +namespace chocolatey.infrastructure.adapters +{ + // ReSharper disable InconsistentNaming + + public interface IHashAlgorithm + { + byte[] ComputeHash(byte[] buffer); + } + + // ReSharper restore InconsistentNaming +} \ No newline at end of file diff --git a/src/chocolatey/infrastructure/cryptography/CryptoHashProviderType.cs b/src/chocolatey/infrastructure/cryptography/CryptoHashProviderType.cs new file mode 100644 index 0000000000..77fe3352a9 --- /dev/null +++ b/src/chocolatey/infrastructure/cryptography/CryptoHashProviderType.cs @@ -0,0 +1,10 @@ +namespace chocolatey.infrastructure.cryptography +{ + public enum CryptoHashProviderType + { + Md5, + Sha1, + Sha256, + Sha512 + } +} \ No newline at end of file diff --git a/src/chocolatey/infrastructure/cryptography/CrytpoHashProvider.cs b/src/chocolatey/infrastructure/cryptography/CrytpoHashProvider.cs new file mode 100644 index 0000000000..95f7c4c0f2 --- /dev/null +++ b/src/chocolatey/infrastructure/cryptography/CrytpoHashProvider.cs @@ -0,0 +1,79 @@ +// Copyright © 2011 - Present RealDimensions Software, LLC +// +// 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.cryptography +{ + using System; + using System.IO; + using System.Security.Cryptography; + using adapters; + using app; + using filesystem; + using Environment = System.Environment; + using HashAlgorithm = adapters.HashAlgorithm; + + + public sealed class CrytpoHashProvider : IHashProvider + { + private readonly IFileSystem _fileSystem; + private readonly IHashAlgorithm _hashAlgorithm; + + public CrytpoHashProvider(IFileSystem fileSystem, CryptoHashProviderType providerType) + { + _fileSystem = fileSystem; + + switch (providerType) + { + case CryptoHashProviderType.Md5: + _hashAlgorithm = new HashAlgorithm(MD5.Create()); + break; + case CryptoHashProviderType.Sha1: + _hashAlgorithm = new HashAlgorithm(SHA1.Create()); + break; + case CryptoHashProviderType.Sha256: + _hashAlgorithm = new HashAlgorithm(SHA256.Create()); + break; + case CryptoHashProviderType.Sha512: + _hashAlgorithm = new HashAlgorithm(SHA512.Create()); + break; + } + } + + public CrytpoHashProvider(IFileSystem fileSystem, IHashAlgorithm hashAlgorithm) + { + _fileSystem = fileSystem; + _hashAlgorithm = hashAlgorithm; + } + + public string hash_file(string filePath) + { + if (!_fileSystem.file_exists(filePath)) return string.Empty; + + try + { + var hash = _hashAlgorithm.ComputeHash(_fileSystem.read_file_bytes(filePath)); + + return BitConverter.ToString(hash).Replace("-", string.Empty); + } + catch (IOException ex) + { + this.Log().Warn(() => "Error computing hash for '{0}'{1} Captured error:{1} {2}".format_with(filePath, Environment.NewLine, ex.Message)); + //IO.IO_FileTooLong2GB (over Int32.MaxValue) + return ApplicationParameters.HashProviderFileTooBig; + return "UnableToDetectChanges_FileTooBig"; + } + } + } +} \ No newline at end of file diff --git a/src/chocolatey/infrastructure/cryptography/Md5HashProvider.cs b/src/chocolatey/infrastructure/cryptography/Md5HashProvider.cs deleted file mode 100644 index e15004bc57..0000000000 --- a/src/chocolatey/infrastructure/cryptography/Md5HashProvider.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright © 2011 - Present RealDimensions Software, LLC -// -// 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.cryptography -{ - using System; - using System.Security.Cryptography; - using filesystem; - - public sealed class Md5HashProvider : IHashProvider - { - private readonly IFileSystem _fileSystem; - private readonly MD5 _cryptoProvider; - - public Md5HashProvider(IFileSystem fileSystem) - { - _fileSystem = fileSystem; - _cryptoProvider = MD5.Create(); - } - - public string hash_file(string filePath) - { - if (!_fileSystem.file_exists(filePath)) return string.Empty; - - var hash = _cryptoProvider.ComputeHash(_fileSystem.read_file_bytes(filePath)); - - return BitConverter.ToString(hash).Replace("-", string.Empty); - } - } -} \ No newline at end of file From 2fdf3bc81f9ecac67a3e9a8e2c375e77066a1bb0 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Tue, 12 May 2015 07:49:27 -0500 Subject: [PATCH 15/27] (GH-121) Remove unchanged files on uninstall When the files that were installed during install are unchanged, they should be removed from the package directory. Any files that have changed should stick around. --- src/chocolatey.tests/chocolatey.tests.csproj | 1 + .../services/NugetServiceSpecs.cs | 166 ++++++++++++++++++ .../services/FilesService.cs | 12 +- .../services/IFilesService.cs | 7 + .../services/NugetService.cs | 24 ++- 5 files changed, 206 insertions(+), 4 deletions(-) create mode 100644 src/chocolatey.tests/infrastructure.app/services/NugetServiceSpecs.cs diff --git a/src/chocolatey.tests/chocolatey.tests.csproj b/src/chocolatey.tests/chocolatey.tests.csproj index 411590c1d4..0afc2017fd 100644 --- a/src/chocolatey.tests/chocolatey.tests.csproj +++ b/src/chocolatey.tests/chocolatey.tests.csproj @@ -78,6 +78,7 @@ + diff --git a/src/chocolatey.tests/infrastructure.app/services/NugetServiceSpecs.cs b/src/chocolatey.tests/infrastructure.app/services/NugetServiceSpecs.cs new file mode 100644 index 0000000000..e15e31af5f --- /dev/null +++ b/src/chocolatey.tests/infrastructure.app/services/NugetServiceSpecs.cs @@ -0,0 +1,166 @@ +// Copyright © 2011 - Present RealDimensions Software, LLC +// +// 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.tests.infrastructure.app.services +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using Moq; + using NuGet; + using chocolatey.infrastructure.app.domain; + using chocolatey.infrastructure.app.services; + using IFileSystem = chocolatey.infrastructure.filesystem.IFileSystem; + + public class NugetServiceSpecs + { + public abstract class NugetServiceSpecsBase : TinySpec + { + protected NugetService service; + protected Mock packageInfoService = new Mock(); + protected Mock fileSystem = new Mock(); + protected Mock nugetLogger = new Mock(); + protected Mock filesService = new Mock(); + protected Mock package = new Mock(); + + public override void Context() + { + fileSystem.ResetCalls(); + nugetLogger.ResetCalls(); + packageInfoService.ResetCalls(); + filesService.ResetCalls(); + package.ResetCalls(); + + service = new NugetService(fileSystem.Object, nugetLogger.Object, packageInfoService.Object, filesService.Object); + } + } + + public class when_NugetService_removes_installation_files_on_uninstall : NugetServiceSpecsBase + { + private Action because; + private ChocolateyPackageInformation packageInfo; + private const string filePath = "c:\\tests"; + private IList packageFiles; + + public override void Context() + { + base.Context(); + package.Setup(x => x.Id).Returns("bob"); + packageInfo = new ChocolateyPackageInformation(package.Object); + packageInfo.FilesSnapshot = new PackageFiles(); + packageFiles = new List(); + fileSystem.Setup(x => x.directory_exists(It.IsAny())).Returns(true); + + } + + public override void Because() + { + because = () => service.remove_installation_files(package.Object, packageInfo); + } + + [Fact] + public void should_do_nothing_if_the_directory_no_longer_exists() + { + Context(); + fileSystem.ResetCalls(); + fileSystem.Setup(x => x.directory_exists(It.IsAny())).Returns(false); + + var packageFile = new PackageFile { Path = filePath, Checksum = "1234" }; + packageFiles.Add(packageFile); + + packageInfo.FilesSnapshot.Files = packageFiles.ToList(); + + var fileSystemFiles = new List() { filePath }; + + fileSystem.Setup(x => x.get_files(It.IsAny(), It.IsAny(), SearchOption.AllDirectories)).Returns(fileSystemFiles); + filesService.Setup(x => x.get_package_file(It.IsAny())).Returns(packageFile); + + because(); + + filesService.Verify(x => x.get_package_file(It.IsAny()), Times.Never); + fileSystem.Verify(x => x.delete_file(filePath),Times.Never); + } + + [Fact] + public void should_remove_an_unchanged_file() + { + Context(); + + var packageFile = new PackageFile { Path = filePath, Checksum = "1234" }; + packageFiles.Add(packageFile); + + packageInfo.FilesSnapshot.Files = packageFiles.ToList(); + + var fileSystemFiles = new List() { filePath }; + + + fileSystem.Setup(x => x.get_files(It.IsAny(), It.IsAny(), SearchOption.AllDirectories)).Returns(fileSystemFiles); + + filesService.Setup(x => x.get_package_file(It.IsAny())).Returns(packageFile); + + because(); + + fileSystem.Verify(x => x.delete_file(filePath)); + } + + [Fact] + public void should_not_delete_a_changed_file() + { + Context(); + + var packageFile = new PackageFile { Path = filePath, Checksum = "1234" }; + var packageFileWithUpdatedChecksum = new PackageFile { Path = filePath, Checksum = "4321" }; + packageFiles.Add(packageFile); + + packageInfo.FilesSnapshot.Files = packageFiles.ToList(); + + var fileSystemFiles = new List() { filePath }; + + + fileSystem.Setup(x => x.get_files(It.IsAny(), It.IsAny(), SearchOption.AllDirectories)).Returns(fileSystemFiles); + + filesService.Setup(x => x.get_package_file(It.IsAny())).Returns(packageFileWithUpdatedChecksum); + + because(); + + fileSystem.Verify(x => x.delete_file(filePath),Times.Never); + } + + [Fact] + public void should_not_delete_an_unfound_file() + { + Context(); + + var packageFile = new PackageFile { Path = filePath, Checksum = "1234" }; + var packageFileNotInOriginal = new PackageFile { Path ="c:\\files", Checksum = "4321" }; + packageFiles.Add(packageFile); + + packageInfo.FilesSnapshot.Files = packageFiles.ToList(); + + var fileSystemFiles = new List() { filePath }; + + + fileSystem.Setup(x => x.get_files(It.IsAny(), It.IsAny(), SearchOption.AllDirectories)).Returns(fileSystemFiles); + + filesService.Setup(x => x.get_package_file(It.IsAny())).Returns(packageFileNotInOriginal); + + because(); + + fileSystem.Verify(x => x.delete_file(filePath),Times.Never); + } + } + } +} \ No newline at end of file diff --git a/src/chocolatey/infrastructure.app/services/FilesService.cs b/src/chocolatey/infrastructure.app/services/FilesService.cs index 2b87891b65..b1793c16d2 100644 --- a/src/chocolatey/infrastructure.app/services/FilesService.cs +++ b/src/chocolatey/infrastructure.app/services/FilesService.cs @@ -74,12 +74,18 @@ public PackageFiles capture_package_files(PackageResult packageResult, Chocolate var files = _fileSystem.get_files(installDirectory, pattern: "*.*", option: SearchOption.AllDirectories); foreach (string file in files.or_empty_list_if_null()) { - var hash = _hashProvider.hash_file(file); - this.Log().Debug(() => " Found '{0}'{1} with checksum '{2}'".format_with(file, Environment.NewLine, hash)); - packageFiles.Files.Add(new PackageFile { Path = file, Checksum = hash}); + packageFiles.Files.Add(get_package_file(file)); } return packageFiles; } + + public PackageFile get_package_file(string file) + { + var hash = _hashProvider.hash_file(file); + this.Log().Debug(() => " Found '{0}'{1} with checksum '{2}'".format_with(file, Environment.NewLine, hash)); + + return new PackageFile { Path = file, Checksum = hash }; + } } } \ No newline at end of file diff --git a/src/chocolatey/infrastructure.app/services/IFilesService.cs b/src/chocolatey/infrastructure.app/services/IFilesService.cs index 7900ac4735..59d2f8c20b 100644 --- a/src/chocolatey/infrastructure.app/services/IFilesService.cs +++ b/src/chocolatey/infrastructure.app/services/IFilesService.cs @@ -45,5 +45,12 @@ public interface IFilesService /// The configuration. /// PackageFiles with entries based on the install location of the package. PackageFiles capture_package_files(PackageResult packageResult, ChocolateyConfiguration config); + + /// + /// Gets a PackageFile from the filepath + /// + /// The file. + /// PackageFile object + PackageFile get_package_file(string file); } } \ No newline at end of file diff --git a/src/chocolatey/infrastructure.app/services/NugetService.cs b/src/chocolatey/infrastructure.app/services/NugetService.cs index 532ac3943d..5c8cacbbf2 100644 --- a/src/chocolatey/infrastructure.app/services/NugetService.cs +++ b/src/chocolatey/infrastructure.app/services/NugetService.cs @@ -42,6 +42,7 @@ public class NugetService : INugetService private readonly IFileSystem _fileSystem; private readonly ILogger _nugetLogger; private readonly IChocolateyPackageInformationService _packageInfoService; + private readonly IFilesService _filesService; private readonly Lazy datetime_initializer = new Lazy(() => new DateTime()); private IDateTime DateTime @@ -55,11 +56,13 @@ private IDateTime DateTime /// The file system. /// The nuget logger /// Package information service - public NugetService(IFileSystem fileSystem, ILogger nugetLogger, IChocolateyPackageInformationService packageInfoService) + /// The files service + public NugetService(IFileSystem fileSystem, ILogger nugetLogger, IChocolateyPackageInformationService packageInfoService, IFilesService filesService) { _fileSystem = fileSystem; _nugetLogger = nugetLogger; _packageInfoService = packageInfoService; + _filesService = filesService; } public void list_noop(ChocolateyConfiguration config) @@ -861,6 +864,7 @@ public ConcurrentDictionary uninstall_run(ChocolateyConfi backup_existing_version(config, packageVersion); packageManager.UninstallPackage(packageVersion, forceRemove: config.Force, removeDependencies: config.ForceDependencies); ensure_nupkg_is_removed(packageVersion, pkgInfo); + remove_installation_files(packageVersion, pkgInfo); } } catch (Exception ex) @@ -900,6 +904,24 @@ private void ensure_nupkg_is_removed(IPackage removedPackage, ChocolateyPackageI _fileSystem.delete_file(nupkg); } + public void remove_installation_files(IPackage removedPackage, ChocolateyPackageInformation pkgInfo) + { + var isSideBySide = pkgInfo != null && pkgInfo.IsSideBySide; + var installDir = _fileSystem.combine_paths(ApplicationParameters.PackagesLocation, "{0}{1}".format_with(removedPackage.Id, isSideBySide ? "." + removedPackage.Version.to_string() : string.Empty)); + + if (_fileSystem.directory_exists(installDir) && pkgInfo != null && pkgInfo.FilesSnapshot != null) + { + foreach (var file in _fileSystem.get_files(installDir, "*.*", SearchOption.AllDirectories).or_empty_list_if_null()) + { + var fileSnapshot = pkgInfo.FilesSnapshot.Files.FirstOrDefault(f => f.Path.is_equal_to(file)); + if (fileSnapshot != null && fileSnapshot.Checksum == _filesService.get_package_file(file).Checksum) + { + _fileSystem.delete_file(file); + } + } + } + } + private void set_package_names_if_all_is_specified(ChocolateyConfiguration config, Action customAction) { if (config.PackageNames.is_equal_to("all")) From b8e05f367b34ed9ea604094899faeeeb7af04288 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Tue, 12 May 2015 07:50:41 -0500 Subject: [PATCH 16/27] (maint) prefix "[NuGet]" on Nuget logger To see messages coming from Nuget, prefix it with "[NuGet]" so that there is no confusion where the messages are coming from. --- .../infrastructure.app/nuget/ChocolateyNugetLogger.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/chocolatey/infrastructure.app/nuget/ChocolateyNugetLogger.cs b/src/chocolatey/infrastructure.app/nuget/ChocolateyNugetLogger.cs index 2b5b8622c5..66318573c5 100644 --- a/src/chocolatey/infrastructure.app/nuget/ChocolateyNugetLogger.cs +++ b/src/chocolatey/infrastructure.app/nuget/ChocolateyNugetLogger.cs @@ -31,16 +31,16 @@ public void Log(MessageLevel level, string message, params object[] args) switch (level) { case MessageLevel.Debug: - this.Log().Debug(message, args); + this.Log().Debug("[NuGet] " + message, args); break; case MessageLevel.Info: - this.Log().Info(message, args); + this.Log().Info("[NuGet] " + message, args); break; case MessageLevel.Warning: - this.Log().Warn(message, args); + this.Log().Warn("[NuGet] " + message, args); break; case MessageLevel.Error: - this.Log().Error(message, args); + this.Log().Error("[NuGet] " + message, args); break; } } From c1d199276ab75e2ed367e37b2d771f947fca906d Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Tue, 12 May 2015 07:50:53 -0500 Subject: [PATCH 17/27] (maint) comment --- src/chocolatey/infrastructure.app/services/NugetService.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/chocolatey/infrastructure.app/services/NugetService.cs b/src/chocolatey/infrastructure.app/services/NugetService.cs index 5c8cacbbf2..835ef7ea43 100644 --- a/src/chocolatey/infrastructure.app/services/NugetService.cs +++ b/src/chocolatey/infrastructure.app/services/NugetService.cs @@ -878,6 +878,7 @@ public ConcurrentDictionary uninstall_run(ChocolateyConfi } else { + // continue action won't be found b/c we are not actually uninstalling (this is noop) var result = packageUninstalls.GetOrAdd(packageVersion.Id.to_lower() + "." + packageVersion.Version.to_string(), new PackageResult(packageVersion, _fileSystem.combine_paths(ApplicationParameters.PackagesLocation, packageVersion.Id))); if (continueAction != null) continueAction.Invoke(result); } From 489a3422d2003892ba3b79e7e44c636824d469dd Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Wed, 13 May 2015 11:24:26 -0500 Subject: [PATCH 18/27] (GH-121) Remove empty directories / scenarios While adding scenarios it was discovered that the empty directories were not being removed, so the service will ensure those are removed upon removing a package with no changed files in it. --- Scenarios.md | 5 ++-- src/chocolatey.tests.integration/Scenario.cs | 27 ++++++++++++++---- .../installpackage/installpackage.1.0.0.nupkg | Bin 4316 -> 4426 bytes .../tools/chocolateyinstall.ps1 | 6 +++- .../filesystem/DotNetFileSystemSpecs.cs | 2 +- .../scenarios/UninstallScenarios.cs | 8 ++++++ .../scenarios/UpgradeScenarios.cs | 20 ++++++------- .../services/NugetService.cs | 5 ++++ 8 files changed, 54 insertions(+), 19 deletions(-) diff --git a/Scenarios.md b/Scenarios.md index 2488a4de8b..95edb7a46d 100644 --- a/Scenarios.md +++ b/Scenarios.md @@ -126,7 +126,7 @@ * should install where install location reports * should not create a shim for ignored executable in the bin directory * should not create a shim for mismatched case ignored executable in the bin directory - * should not create an extensions folder for the pacakge + * should not create an extensions folder for the package * should not have inconclusive package result * should not have warning package result @@ -315,7 +315,7 @@ * should not have inconclusive package result * should not have warning package result -### ChocolateyUninstallCommand [ 12 Scenario(s), 82 Observation(s) ] +### ChocolateyUninstallCommand [ 12 Scenario(s), 83 Observation(s) ] #### when force uninstalling a package @@ -359,6 +359,7 @@ * should contain a warning message that it uninstalled successfully * should delete a shim for console in the bin directory * should delete a shim for graphical in the bin directory + * should delete any files created during the install * should delete the rollback * should have a successful package result * should not have inconclusive package result diff --git a/src/chocolatey.tests.integration/Scenario.cs b/src/chocolatey.tests.integration/Scenario.cs index 9147f0fe07..08fa4ae024 100644 --- a/src/chocolatey.tests.integration/Scenario.cs +++ b/src/chocolatey.tests.integration/Scenario.cs @@ -23,10 +23,14 @@ namespace chocolatey.tests.integration using chocolatey.infrastructure.app.configuration; using chocolatey.infrastructure.app.domain; using chocolatey.infrastructure.app.nuget; + using chocolatey.infrastructure.app.services; using chocolatey.infrastructure.filesystem; public class Scenario { + + private static IChocolateyPackageService _service; + private static readonly DotNetFileSystem _fileSystem = new DotNetFileSystem(); public static string get_top_level() @@ -79,13 +83,26 @@ public static void add_packages_to_source_location(ChocolateyConfiguration confi public static void install_package(ChocolateyConfiguration config, string packageId, string version) { - var pattern = "{0}.{1}{2}".format_with(packageId, string.IsNullOrWhiteSpace(version) ? "*" : version, Constants.PackageExtension); - var files = _fileSystem.get_files(config.Sources, pattern); - foreach (var file in files) + if (_service == null) { - var packageManager = NugetCommon.GetPackageManager(config,new ChocolateyNugetLogger(), null, null, false); - packageManager.InstallPackage(new OptimizedZipPackage(file), false,false); + _service= NUnitSetup.Container.GetInstance(); } + + var originalPackageName = config.PackageNames; + var originalPackageVersion = config.Version; + + config.PackageNames = packageId; + config.Version = version; + _service.install_run(config); + config.PackageNames = originalPackageName; + config.Version = originalPackageVersion; + //var pattern = "{0}.{1}{2}".format_with(packageId, string.IsNullOrWhiteSpace(version) ? "*" : version, Constants.PackageExtension); + //var files = _fileSystem.get_files(config.Sources, pattern); + //foreach (var file in files) + //{ + // var packageManager = NugetCommon.GetPackageManager(config, new ChocolateyNugetLogger(), null, null, false); + // packageManager.InstallPackage(new OptimizedZipPackage(file), false,false); + //} } private static ChocolateyConfiguration baseline_configuration() diff --git a/src/chocolatey.tests.integration/context/installpackage/installpackage.1.0.0.nupkg b/src/chocolatey.tests.integration/context/installpackage/installpackage.1.0.0.nupkg index 749cb1a648b058f51f6288cfa9f58e662381b32c..277f38fee29acfeba1ac437f513e3502b3123d02 100644 GIT binary patch delta 802 zcmcbkcuGkqz?+$civa{;ZP&PcR=#YyQAmkV*wD<#)G*o7!pOif#U#}rO-Vt2;z50U}E3^IbpLaQ!*nnkTH2R zvp<;r%N)iFa+~OZTSCi#?mRU)fyJ)gx7U{Ikb^+$Z-;jVI+|N<$T(~;;?P{g=qNo~ zb>@o)|2EEKVZL|fka8ufc}K$cikkP;@1JWYKQD<7XfW-_Df}=i;#${wg}q!ElAE%$ z-oB7J`XndLS313CQ*K;>`?P08nwMO@r0j8f;jy@Z{SEuH+cOq_`Cjl($#~-DxvVG7 zwVJS9R`K;~lt{hvqwz7*+a0Syi-X1QXf}U{mofbEQP}JD=1Wr1hTN!roBWui5A3B9 zRxdDpkktoF3$O)1Y4OQbY?fg5HnwmMkhf>HOj|iwl-*pOK)7$tW)EUwG@ZPUa}G%V z=2)%+Oke{*aXML**AQ%KB(DRQp2O=1rXTQnparfepN#^@-nP9ax2%A{?F7WKK#Tw` ztdl45X@j-z=aXi`uLf3ql3$uhoPF{_e#y!I`Gw_S{y)UTzyQL52uA=}g_8{h^a2S4 i3p6=GtVJaBH#!MwB|sBFSQy3BeUmo`sIk@af&>9$ru*Cg delta 687 zcmX@5bVpGrz?+$civa|R>Vn*!HvBfi&O&>Qv*|TgS2G5g5uniJnl=F=TQeYadweBUUdk ztX%+TxVUYPpUsTUK#Kgb=!hsAjlNSo;$P@7E=9}z6OpFGT zb-3n$jM{vf>i`o6$ShDePM*wb2)5ujuLGFo;&TMketaHi9$L(2qX^R0w%6pA70`=L zKr9Qy2yl&sVR9m?++;a^9bi`VT7!ddGdS# ny+8ut1x*_eHz88p8=VBT5}*S>SQyz*hJeX(f@*AnydXgU^T)9< diff --git a/src/chocolatey.tests.integration/context/installpackage/tools/chocolateyinstall.ps1 b/src/chocolatey.tests.integration/context/installpackage/tools/chocolateyinstall.ps1 index d64eb8f47b..2491151c21 100644 --- a/src/chocolatey.tests.integration/context/installpackage/tools/chocolateyinstall.ps1 +++ b/src/chocolatey.tests.integration/context/installpackage/tools/chocolateyinstall.ps1 @@ -1 +1,5 @@ -Write-Output "$env:PackageName $env:PackageVersion Installed" \ No newline at end of file +$toolsDir = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)" +"simple file" | Out-File "$toolsDir\simplefile.txt" -force + +Write-Output "$env:PackageName $env:PackageVersion Installed" + diff --git a/src/chocolatey.tests.integration/infrastructure/filesystem/DotNetFileSystemSpecs.cs b/src/chocolatey.tests.integration/infrastructure/filesystem/DotNetFileSystemSpecs.cs index 7d032c0b37..653fb137c3 100644 --- a/src/chocolatey.tests.integration/infrastructure/filesystem/DotNetFileSystemSpecs.cs +++ b/src/chocolatey.tests.integration/infrastructure/filesystem/DotNetFileSystemSpecs.cs @@ -233,7 +233,7 @@ public override void Because() { if (FileSystem.directory_exists(TestDirectory)) { - Directory.Delete(TestDirectory); + Directory.Delete(TestDirectory, recursive: true); } FileSystem.create_directory(TestDirectory); diff --git a/src/chocolatey.tests.integration/scenarios/UninstallScenarios.cs b/src/chocolatey.tests.integration/scenarios/UninstallScenarios.cs index 15b3851dce..d207d7153c 100644 --- a/src/chocolatey.tests.integration/scenarios/UninstallScenarios.cs +++ b/src/chocolatey.tests.integration/scenarios/UninstallScenarios.cs @@ -175,6 +175,14 @@ public void should_delete_a_shim_for_graphical_in_the_bin_directory() File.Exists(shimfile).ShouldBeFalse(); } + [Fact] + public void should_delete_any_files_created_during_the_install() + { + var generatedFile = Path.Combine(Scenario.get_top_level(), "lib", Configuration.PackageNames,"simplefile.txt"); + + File.Exists(generatedFile).ShouldBeFalse(); + } + [Fact] public void should_contain_a_warning_message_that_it_uninstalled_successfully() { diff --git a/src/chocolatey.tests.integration/scenarios/UpgradeScenarios.cs b/src/chocolatey.tests.integration/scenarios/UpgradeScenarios.cs index 12ae77dace..48f2e50ad8 100644 --- a/src/chocolatey.tests.integration/scenarios/UpgradeScenarios.cs +++ b/src/chocolatey.tests.integration/scenarios/UpgradeScenarios.cs @@ -1562,7 +1562,7 @@ public override void Context() string dotChocolatey = Path.Combine(Scenario.get_top_level(), ".chocolatey"); if (Directory.Exists(dotChocolatey)) { - Directory.Delete(dotChocolatey); + Directory.Delete(dotChocolatey, recursive: true); } } @@ -1584,8 +1584,8 @@ public void should_remove_the_legacy_folder_version_of_the_package() { var packageDir = Path.Combine(Scenario.get_top_level(), "lib", "isdependency.1.0.0"); Directory.Exists(packageDir).ShouldBeFalse(); - } - + } + [Fact] public void should_replace_the_legacy_folder_version_of_the_package_with_a_lib_package_folder_that_has_no_version() { @@ -1599,15 +1599,15 @@ public void should_not_upgrade_the_parent_package() var packageFile = Path.Combine(Scenario.get_top_level(), "lib", "hasdependency.1.0.0", "hasdependency.1.0.0.nupkg"); var package = new OptimizedZipPackage(packageFile); package.Version.Version.to_string().ShouldEqual("1.0.0.0"); - } - + } + [Fact] public void should_not_add_a_versionless_parent_package_folder_to_the_lib_dir() { var packageDir = Path.Combine(Scenario.get_top_level(), "lib", "hasdependency"); Directory.Exists(packageDir).ShouldBeFalse(); - } - + } + [Fact] public void should_leave_the_parent_package_as_legacy_folder() { @@ -1635,7 +1635,7 @@ public void should_not_add_a_versionless_exact_version_package_folder_to_the_lib { var packageDir = Path.Combine(Scenario.get_top_level(), "lib", "isexactversiondependency"); Directory.Exists(packageDir).ShouldBeFalse(); - } + } [Fact] public void should_contain_a_message_the_dependency_upgraded_successfully() @@ -1778,7 +1778,7 @@ public override void Context() string dotChocolatey = Path.Combine(Scenario.get_top_level(), ".chocolatey"); if (Directory.Exists(dotChocolatey)) { - Directory.Delete(dotChocolatey); + Directory.Delete(dotChocolatey, recursive: true); } } @@ -1853,7 +1853,7 @@ public void should_remove_the_legacy_folder_version_of_the_exact_version_package { var packageDir = Path.Combine(Scenario.get_top_level(), "lib", "isexactversiondependency.1.0.0"); Directory.Exists(packageDir).ShouldBeFalse(); - } + } [Fact] public void should_contain_a_message_that_everything_upgraded_successfully() diff --git a/src/chocolatey/infrastructure.app/services/NugetService.cs b/src/chocolatey/infrastructure.app/services/NugetService.cs index 835ef7ea43..34d04e26f2 100644 --- a/src/chocolatey/infrastructure.app/services/NugetService.cs +++ b/src/chocolatey/infrastructure.app/services/NugetService.cs @@ -921,6 +921,11 @@ public void remove_installation_files(IPackage removedPackage, ChocolateyPackage } } } + + if (_fileSystem.directory_exists(installDir) && !_fileSystem.get_files(installDir, "*.*", SearchOption.AllDirectories).or_empty_list_if_null().Any()) + { + _fileSystem.delete_directory_if_exists(installDir, recursive: true); + } } private void set_package_names_if_all_is_specified(ChocolateyConfiguration config, Action customAction) From 26ebf4e48e3452aa9dff9099bbbe0948d730e448 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Fri, 15 May 2015 06:02:41 -0500 Subject: [PATCH 19/27] (maint) renaming results to appropriate singular Results is actually a singular result, so the naming is off. Fix that to be appropriate. --- .../services/NugetService.cs | 99 ++++++++++--------- 1 file changed, 50 insertions(+), 49 deletions(-) diff --git a/src/chocolatey/infrastructure.app/services/NugetService.cs b/src/chocolatey/infrastructure.app/services/NugetService.cs index 34d04e26f2..ffb71c025f 100644 --- a/src/chocolatey/infrastructure.app/services/NugetService.cs +++ b/src/chocolatey/infrastructure.app/services/NugetService.cs @@ -268,11 +268,11 @@ public ConcurrentDictionary install_run(ChocolateyConfigu installSuccessAction: (e) => { var pkg = e.Package; - var results = packageInstalls.GetOrAdd(pkg.Id.to_lower(), new PackageResult(pkg, e.InstallPath)); - results.InstallLocation = e.InstallPath; - results.Messages.Add(new ResultMessage(ResultType.Debug, ApplicationParameters.Messages.ContinueChocolateyAction)); + var packageResult = packageInstalls.GetOrAdd(pkg.Id.to_lower(), new PackageResult(pkg, e.InstallPath)); + packageResult.InstallLocation = e.InstallPath; + packageResult.Messages.Add(new ResultMessage(ResultType.Debug, ApplicationParameters.Messages.ContinueChocolateyAction)); - if (continueAction != null) continueAction.Invoke(results); + if (continueAction != null) continueAction.Invoke(packageResult); }, uninstallSuccessAction: null, addUninstallHandler: true); @@ -289,9 +289,9 @@ public ConcurrentDictionary install_run(ChocolateyConfigu if (installedPackage != null && (version == null || version == installedPackage.Version) && !config.Force) { string logMessage = "{0} v{1} already installed.{2} Use --force to reinstall, specify a version to install, or try upgrade.".format_with(installedPackage.Id, installedPackage.Version, Environment.NewLine); - var results = packageInstalls.GetOrAdd(packageName, new PackageResult(installedPackage, _fileSystem.combine_paths(ApplicationParameters.PackagesLocation, installedPackage.Id))); - results.Messages.Add(new ResultMessage(ResultType.Warn, logMessage)); - results.Messages.Add(new ResultMessage(ResultType.Inconclusive, logMessage)); + var nullResult = packageInstalls.GetOrAdd(packageName, new PackageResult(installedPackage, _fileSystem.combine_paths(ApplicationParameters.PackagesLocation, installedPackage.Id))); + nullResult.Messages.Add(new ResultMessage(ResultType.Warn, logMessage)); + nullResult.Messages.Add(new ResultMessage(ResultType.Inconclusive, logMessage)); this.Log().Warn(ChocolateyLoggers.Important, logMessage); continue; } @@ -307,31 +307,31 @@ public ConcurrentDictionary install_run(ChocolateyConfigu { var logMessage = "{0} not installed. The package was not found with the source(s) listed.{1} If you specified a particular version and are receiving this message, it is possible that the package name exists but the version does not.{1} Version: \"{2}\"{1} Source(s): \"{3}\"".format_with(packageName, Environment.NewLine, config.Version, config.Sources); this.Log().Error(ChocolateyLoggers.Important, logMessage); - var results = packageInstalls.GetOrAdd(packageName, new PackageResult(packageName, version.to_string(), null)); - results.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); + var noPkgResult = packageInstalls.GetOrAdd(packageName, new PackageResult(packageName, version.to_string(), null)); + noPkgResult.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); continue; } if (installedPackage != null && (installedPackage.Version == availablePackage.Version) && config.Force) { - var results = packageInstalls.GetOrAdd(packageName, new PackageResult(installedPackage, _fileSystem.combine_paths(ApplicationParameters.PackagesLocation, installedPackage.Id))); - results.Messages.Add(new ResultMessage(ResultType.Note, "Backing up and removing old version")); + var forcedResult = packageInstalls.GetOrAdd(packageName, new PackageResult(installedPackage, _fileSystem.combine_paths(ApplicationParameters.PackagesLocation, installedPackage.Id))); + forcedResult.Messages.Add(new ResultMessage(ResultType.Note, "Backing up and removing old version")); backup_existing_version(config, installedPackage); try { packageManager.UninstallPackage(installedPackage, forceRemove: config.Force, removeDependencies: config.ForceDependencies); - if (!results.InstallLocation.is_equal_to(ApplicationParameters.PackagesLocation)) + if (!forcedResult.InstallLocation.is_equal_to(ApplicationParameters.PackagesLocation)) { - _fileSystem.delete_directory_if_exists(results.InstallLocation, recursive: true); + _fileSystem.delete_directory_if_exists(forcedResult.InstallLocation, recursive: true); } } catch (Exception ex) { string logMessage = "{0}:{1} {2}".format_with("Unable to remove existing package prior to forced reinstall", Environment.NewLine, ex.Message); this.Log().Warn(logMessage); - results.Messages.Add(new ResultMessage(ResultType.Inconclusive, logMessage)); + forcedResult.Messages.Add(new ResultMessage(ResultType.Inconclusive, logMessage)); } } @@ -350,9 +350,9 @@ public ConcurrentDictionary install_run(ChocolateyConfigu { var logMessage = "{0} not installed. An error occurred during installation:{1} {2}".format_with(packageName, Environment.NewLine, ex.Message); this.Log().Error(ChocolateyLoggers.Important, logMessage); - var results = packageInstalls.GetOrAdd(packageName, new PackageResult(packageName, version.to_string(), null)); - results.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); - if (continueAction != null) continueAction.Invoke(results); + var errorResult = packageInstalls.GetOrAdd(packageName, new PackageResult(packageName, version.to_string(), null)); + errorResult.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); + if (continueAction != null) continueAction.Invoke(errorResult); } } @@ -403,11 +403,11 @@ public ConcurrentDictionary upgrade_run(ChocolateyConfigu installSuccessAction: (e) => { var pkg = e.Package; - var results = packageInstalls.GetOrAdd(pkg.Id.to_lower(), new PackageResult(pkg, e.InstallPath)); - results.InstallLocation = e.InstallPath; - results.Messages.Add(new ResultMessage(ResultType.Debug, ApplicationParameters.Messages.ContinueChocolateyAction)); + var packageResult = packageInstalls.GetOrAdd(pkg.Id.to_lower(), new PackageResult(pkg, e.InstallPath)); + packageResult.InstallLocation = e.InstallPath; + packageResult.Messages.Add(new ResultMessage(ResultType.Debug, ApplicationParameters.Messages.ContinueChocolateyAction)); - if (continueAction != null) continueAction.Invoke(results); + if (continueAction != null) continueAction.Invoke(packageResult); }, uninstallSuccessAction: null, addUninstallHandler: false); @@ -426,8 +426,8 @@ public ConcurrentDictionary upgrade_run(ChocolateyConfigu { //todo v1 Deprecation - reimplement error //string logMessage = "{0} is not installed. Cannot upgrade a non-existent package.".format_with(packageName); - //var results = packageInstalls.GetOrAdd(packageName, new PackageResult(packageName, null, null)); - //results.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); + //var result = packageInstalls.GetOrAdd(packageName, new PackageResult(packageName, null, null)); + //result.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); //if (config.RegularOutput) this.Log().Error(ChocolateyLoggers.Important, logMessage); //continue; @@ -450,9 +450,9 @@ packages as of version 1.0.0. That is what the install command is for. else { var installResults = install_run(config, continueAction); - foreach (var packageResult in installResults) + foreach (var result in installResults) { - packageInstalls.GetOrAdd(packageResult.Key, packageResult.Value); + packageInstalls.GetOrAdd(result.Key, result.Value); } } @@ -468,17 +468,17 @@ packages as of version 1.0.0. That is what the install command is for. if (availablePackage == null) { string logMessage = "{0} was not found with the source(s) listed.{1} If you specified a particular version and are receiving this message, it is possible that the package name exists but the version does not.{1} Version: \"{2}\"{1} Source(s): \"{3}\"".format_with(packageName, Environment.NewLine, config.Version, config.Sources); - var unfoundResults = packageInstalls.GetOrAdd(packageName, new PackageResult(packageName, version.to_string(), null)); + var unfoundResult = packageInstalls.GetOrAdd(packageName, new PackageResult(packageName, version.to_string(), null)); if (config.UpgradeCommand.FailOnUnfound) { - unfoundResults.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); + unfoundResult.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); if (config.RegularOutput) this.Log().Error(ChocolateyLoggers.Important, logMessage); } else { - unfoundResults.Messages.Add(new ResultMessage(ResultType.Warn, logMessage)); - unfoundResults.Messages.Add(new ResultMessage(ResultType.Inconclusive, logMessage)); + unfoundResult.Messages.Add(new ResultMessage(ResultType.Warn, logMessage)); + unfoundResult.Messages.Add(new ResultMessage(ResultType.Inconclusive, logMessage)); if (config.RegularOutput) { this.Log().Warn(ChocolateyLoggers.Important, logMessage); @@ -498,12 +498,12 @@ packages as of version 1.0.0. That is what the install command is for. //todo: get smarter about realizing multiple versions have been installed before and allowing that } - var results = packageInstalls.GetOrAdd(packageName, new PackageResult(availablePackage, _fileSystem.combine_paths(ApplicationParameters.PackagesLocation, availablePackage.Id))); + var packageResult = packageInstalls.GetOrAdd(packageName, new PackageResult(availablePackage, _fileSystem.combine_paths(ApplicationParameters.PackagesLocation, availablePackage.Id))); if ((installedPackage.Version > availablePackage.Version)) { string logMessage = "{0} v{1} is newer than the most recent.{2} You must be smarter than the average bear...".format_with(installedPackage.Id, installedPackage.Version, Environment.NewLine); - results.Messages.Add(new ResultMessage(ResultType.Inconclusive, logMessage)); + packageResult.Messages.Add(new ResultMessage(ResultType.Inconclusive, logMessage)); if (config.RegularOutput) { @@ -522,9 +522,9 @@ packages as of version 1.0.0. That is what the install command is for. if (!config.Force) { - if (results.Messages.Count((p) => p.Message == ApplicationParameters.Messages.ContinueChocolateyAction) == 0) + if (packageResult.Messages.Count((p) => p.Message == ApplicationParameters.Messages.ContinueChocolateyAction) == 0) { - results.Messages.Add(new ResultMessage(ResultType.Inconclusive, logMessage)); + packageResult.Messages.Add(new ResultMessage(ResultType.Inconclusive, logMessage)); } if (config.RegularOutput) @@ -539,7 +539,7 @@ packages as of version 1.0.0. That is what the install command is for. continue; } - results.Messages.Add(new ResultMessage(ResultType.Note, logMessage)); + packageResult.Messages.Add(new ResultMessage(ResultType.Note, logMessage)); if (config.RegularOutput) this.Log().Info(logMessage); } @@ -548,7 +548,7 @@ packages as of version 1.0.0. That is what the install command is for. if (availablePackage.Version > installedPackage.Version) { string logMessage = "You have {0} v{1} installed. Version {2} is available based on your source(s)".format_with(installedPackage.Id, installedPackage.Version, availablePackage.Version); - results.Messages.Add(new ResultMessage(ResultType.Note, logMessage)); + packageResult.Messages.Add(new ResultMessage(ResultType.Note, logMessage)); if (config.RegularOutput) { @@ -563,8 +563,8 @@ packages as of version 1.0.0. That is what the install command is for. if (isPinned) { string logMessage = "{0} is pinned. Skipping pinned package.".format_with(packageName); - results.Messages.Add(new ResultMessage(ResultType.Warn, logMessage)); - results.Messages.Add(new ResultMessage(ResultType.Inconclusive, logMessage)); + packageResult.Messages.Add(new ResultMessage(ResultType.Warn, logMessage)); + packageResult.Messages.Add(new ResultMessage(ResultType.Inconclusive, logMessage)); if (config.RegularOutput) this.Log().Warn(ChocolateyLoggers.Important, logMessage); continue; @@ -598,8 +598,8 @@ packages as of version 1.0.0. That is what the install command is for. { var logMessage = "{0} not upgraded. An error occurred during installation:{1} {2}".format_with(packageName, Environment.NewLine, ex.Message); this.Log().Error(ChocolateyLoggers.Important, logMessage); - results.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); - if (continueAction != null) continueAction.Invoke(results); + packageResult.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); + if (continueAction != null) continueAction.Invoke(packageResult); } } } @@ -733,11 +733,12 @@ public ConcurrentDictionary uninstall_run(ChocolateyConfi var pkg = e.Package; // this section fires twice sometimes, like for older packages in a sxs install... - var results = packageUninstalls.GetOrAdd(pkg.Id.to_lower() + "." + pkg.Version.to_string(), new PackageResult(pkg, e.InstallPath)); + var packageResult = packageUninstalls.GetOrAdd(pkg.Id.to_lower() + "." + pkg.Version.to_string(), new PackageResult(pkg, e.InstallPath)); + packageResult.InstallLocation = e.InstallPath; string logMessage = "{0}{1} v{2}{3}".format_with(Environment.NewLine, pkg.Id, pkg.Version.to_string(), config.Force ? " (forced)" : string.Empty); - if (results.Messages.Count((p) => p.Message == ApplicationParameters.Messages.NugetEventActionHeader) == 0) + if (packageResult.Messages.Count((p) => p.Message == ApplicationParameters.Messages.NugetEventActionHeader) == 0) { - results.Messages.Add(new ResultMessage(ResultType.Debug, ApplicationParameters.Messages.NugetEventActionHeader)); + packageResult.Messages.Add(new ResultMessage(ResultType.Debug, ApplicationParameters.Messages.NugetEventActionHeader)); "chocolatey".Log().Info(ChocolateyLoggers.Important, logMessage); loopCount = 0; } @@ -757,8 +758,8 @@ public ConcurrentDictionary uninstall_run(ChocolateyConfi var latestVersion = packageManager.LocalRepository.FindPackage(e.Package.Id); if (latestVersion.Version == pkg.Version || config.AllowMultipleVersions) { - results.Messages.Add(new ResultMessage(ResultType.Debug, ApplicationParameters.Messages.ContinueChocolateyAction)); - if (continueAction != null) continueAction.Invoke(results); + packageResult.Messages.Add(new ResultMessage(ResultType.Debug, ApplicationParameters.Messages.ContinueChocolateyAction)); + if (continueAction != null) continueAction.Invoke(packageResult); } else { @@ -792,8 +793,8 @@ public ConcurrentDictionary uninstall_run(ChocolateyConfi if (installedPackageVersions.Count == 0) { string logMessage = "{0} is not installed. Cannot uninstall a non-existent package.".format_with(packageName); - var results = packageUninstalls.GetOrAdd(packageName, new PackageResult(packageName, null, null)); - results.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); + var missingResult = packageUninstalls.GetOrAdd(packageName, new PackageResult(packageName, null, null)); + missingResult.Messages.Add(new ResultMessage(ResultType.Error, logMessage)); if (config.RegularOutput) this.Log().Error(ChocolateyLoggers.Important, logMessage); continue; @@ -844,9 +845,9 @@ public ConcurrentDictionary uninstall_run(ChocolateyConfi if (pkgInfo != null && pkgInfo.IsPinned) { string logMessage = "{0} is pinned. Skipping pinned package.".format_with(packageName); - var pinnedResults = packageUninstalls.GetOrAdd(packageName, new PackageResult(packageName, null, null)); - pinnedResults.Messages.Add(new ResultMessage(ResultType.Warn, logMessage)); - pinnedResults.Messages.Add(new ResultMessage(ResultType.Inconclusive, logMessage)); + var pinnedResult = packageUninstalls.GetOrAdd(packageName, new PackageResult(packageName, null, null)); + pinnedResult.Messages.Add(new ResultMessage(ResultType.Warn, logMessage)); + pinnedResult.Messages.Add(new ResultMessage(ResultType.Inconclusive, logMessage)); if (config.RegularOutput) this.Log().Warn(ChocolateyLoggers.Important, logMessage); continue; } From 7f7b277ce437cb80901de5cd0fd26133a321f64b Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Fri, 15 May 2015 06:03:45 -0500 Subject: [PATCH 20/27] (GH-121) Remove package checksum The entire package checksum is not needed at this time so remove it. --- src/chocolatey/infrastructure.app/domain/PackageFiles.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/chocolatey/infrastructure.app/domain/PackageFiles.cs b/src/chocolatey/infrastructure.app/domain/PackageFiles.cs index a36ca65c1a..f7f53b20d1 100644 --- a/src/chocolatey/infrastructure.app/domain/PackageFiles.cs +++ b/src/chocolatey/infrastructure.app/domain/PackageFiles.cs @@ -56,13 +56,5 @@ public PackageFiles(IEnumerable files) [XmlArray("files")] public List Files { get; set; } - /// - /// Gets or sets the package checksum. - /// - /// - /// The package checksum. - /// - [XmlElement(ElementName = "packageChecksum")] - public string PackageChecksum { get; set; } } } \ No newline at end of file From 40b443712e49cbd2cb5c9988c227835f2dea9421 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Fri, 15 May 2015 06:05:28 -0500 Subject: [PATCH 21/27] (GH-121) IFilesService capture files by directory --- .../infrastructure.app/services/FilesService.cs | 17 +++++++++++++++-- .../services/IFilesService.cs | 8 ++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/chocolatey/infrastructure.app/services/FilesService.cs b/src/chocolatey/infrastructure.app/services/FilesService.cs index b1793c16d2..462e7246af 100644 --- a/src/chocolatey/infrastructure.app/services/FilesService.cs +++ b/src/chocolatey/infrastructure.app/services/FilesService.cs @@ -67,11 +67,23 @@ public PackageFiles capture_package_files(PackageResult packageResult, Chocolate return null; } + return capture_package_files(installDirectory, config); + } + + public PackageFiles capture_package_files(string directory, ChocolateyConfiguration config) + { + if (directory.is_equal_to(ApplicationParameters.InstallLocation) || directory.is_equal_to(ApplicationParameters.PackagesLocation)) + { + var logMessage = "Install location is not specific enough, cannot capture files:{0} Erroneous install location captured as '{1}'".format_with(Environment.NewLine, directory); + this.Log().Error(logMessage); + return null; + } + var packageFiles = new PackageFiles(); - this.Log().Debug(() => "Capturing package files in '{0}'".format_with(installDirectory)); + this.Log().Debug(() => "Capturing package files in '{0}'".format_with(directory)); //gather all files in the folder - var files = _fileSystem.get_files(installDirectory, pattern: "*.*", option: SearchOption.AllDirectories); + var files = _fileSystem.get_files(directory, pattern: "*.*", option: SearchOption.AllDirectories); foreach (string file in files.or_empty_list_if_null()) { packageFiles.Files.Add(get_package_file(file)); @@ -80,6 +92,7 @@ public PackageFiles capture_package_files(PackageResult packageResult, Chocolate return packageFiles; } + public PackageFile get_package_file(string file) { var hash = _hashProvider.hash_file(file); diff --git a/src/chocolatey/infrastructure.app/services/IFilesService.cs b/src/chocolatey/infrastructure.app/services/IFilesService.cs index 59d2f8c20b..09687a0eaf 100644 --- a/src/chocolatey/infrastructure.app/services/IFilesService.cs +++ b/src/chocolatey/infrastructure.app/services/IFilesService.cs @@ -46,6 +46,14 @@ public interface IFilesService /// PackageFiles with entries based on the install location of the package. PackageFiles capture_package_files(PackageResult packageResult, ChocolateyConfiguration config); + /// + /// Captures the snapshot of the package files + /// + /// The directory. + /// The configuration. + /// PackageFiles with entries based on the install location of the package. + PackageFiles capture_package_files(string directory, ChocolateyConfiguration config); + /// /// Gets a PackageFile from the filepath /// From 9a68ebd084cb6334c8b5e094994f7003cc8d13e6 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Fri, 15 May 2015 07:52:42 -0500 Subject: [PATCH 22/27] (GH-121) backup changed files When files have changed in a package, back those files up prior to upgrade. --- .../services/FilesServiceSpecs.cs | 10 ++- .../services/NugetServiceSpecs.cs | 76 ++++++++++++++++--- .../services/FilesService.cs | 10 +-- .../services/NugetService.cs | 49 +++++++++--- 4 files changed, 115 insertions(+), 30 deletions(-) diff --git a/src/chocolatey.tests/infrastructure.app/services/FilesServiceSpecs.cs b/src/chocolatey.tests/infrastructure.app/services/FilesServiceSpecs.cs index dd8bf96c41..5f4b471d9b 100644 --- a/src/chocolatey.tests/infrastructure.app/services/FilesServiceSpecs.cs +++ b/src/chocolatey.tests/infrastructure.app/services/FilesServiceSpecs.cs @@ -192,9 +192,15 @@ public void should_not_call_get_files() } [Fact] - public void should_return_null() + public void should_return_a_non_null_object() { - result.ShouldBeNull(); + result.ShouldNotBeNull(); + } + + [Fact] + public void should_return_empty_package_files() + { + result.Files.ShouldBeEmpty(); } } diff --git a/src/chocolatey.tests/infrastructure.app/services/NugetServiceSpecs.cs b/src/chocolatey.tests/infrastructure.app/services/NugetServiceSpecs.cs index e15e31af5f..1f89ade817 100644 --- a/src/chocolatey.tests/infrastructure.app/services/NugetServiceSpecs.cs +++ b/src/chocolatey.tests/infrastructure.app/services/NugetServiceSpecs.cs @@ -21,6 +21,7 @@ namespace chocolatey.tests.infrastructure.app.services using System.Linq; using Moq; using NuGet; + using chocolatey.infrastructure.app.configuration; using chocolatey.infrastructure.app.domain; using chocolatey.infrastructure.app.services; using IFileSystem = chocolatey.infrastructure.filesystem.IFileSystem; @@ -48,6 +49,70 @@ public override void Context() } } + public class when_NugetService_backs_up_changed_files : NugetServiceSpecsBase + { + private Action because; + private ChocolateyPackageInformation packageInfo; + private const string filePath = "c:\\tests"; + private PackageFiles packageFiles; + private readonly ChocolateyConfiguration config = new ChocolateyConfiguration(); + + public override void Context() + { + base.Context(); + package.Setup(x => x.Id).Returns("bob"); + packageInfo = new ChocolateyPackageInformation(package.Object); + packageInfo.FilesSnapshot = new PackageFiles(); + packageFiles = new PackageFiles(); + fileSystem.Setup(x => x.directory_exists(It.IsAny())).Returns(true); + } + + public override void Because() + { + because = () => service.backup_changed_files(filePath, config, packageInfo); + } + + [Fact] + public void should_ignore_an_unchanged_file() + { + Context(); + + var packageFile = new PackageFile { Path = filePath, Checksum = "1234" }; + packageFiles.Files.Add(packageFile); + packageInfo.FilesSnapshot = packageFiles; + + var fileSystemFiles = new List() { filePath }; + fileSystem.Setup(x => x.get_files(It.IsAny(), It.IsAny(), SearchOption.AllDirectories)).Returns(fileSystemFiles); + filesService.Setup(x => x.capture_package_files(It.IsAny(),config)).Returns(packageFiles); + + because(); + + fileSystem.Verify(x => x.copy_file(It.IsAny(),It.IsAny(),It.IsAny()),Times.Never); + } + + [Fact] + public void should_backup_a_changed_file() + { + Context(); + + var packageFile = new PackageFile { Path = filePath, Checksum = "1234" }; + packageFiles.Files.Add(packageFile); + packageInfo.FilesSnapshot = packageFiles; + + var packageFileWithUpdatedChecksum = new PackageFile { Path = filePath, Checksum = "4321" }; + + var fileSystemFiles = new List() { filePath }; + fileSystem.Setup(x => x.get_files(It.IsAny(), It.IsAny(), SearchOption.AllDirectories)).Returns(fileSystemFiles); + var updatedPackageFiles = new PackageFiles(); + updatedPackageFiles.Files = new List{packageFileWithUpdatedChecksum}; + filesService.Setup(x => x.capture_package_files(It.IsAny(), config)).Returns(updatedPackageFiles); + + because(); + + fileSystem.Verify(x => x.copy_file(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + } + } + public class when_NugetService_removes_installation_files_on_uninstall : NugetServiceSpecsBase { private Action because; @@ -63,7 +128,6 @@ public override void Context() packageInfo.FilesSnapshot = new PackageFiles(); packageFiles = new List(); fileSystem.Setup(x => x.directory_exists(It.IsAny())).Returns(true); - } public override void Because() @@ -80,11 +144,9 @@ public void should_do_nothing_if_the_directory_no_longer_exists() var packageFile = new PackageFile { Path = filePath, Checksum = "1234" }; packageFiles.Add(packageFile); - packageInfo.FilesSnapshot.Files = packageFiles.ToList(); var fileSystemFiles = new List() { filePath }; - fileSystem.Setup(x => x.get_files(It.IsAny(), It.IsAny(), SearchOption.AllDirectories)).Returns(fileSystemFiles); filesService.Setup(x => x.get_package_file(It.IsAny())).Returns(packageFile); @@ -101,14 +163,10 @@ public void should_remove_an_unchanged_file() var packageFile = new PackageFile { Path = filePath, Checksum = "1234" }; packageFiles.Add(packageFile); - packageInfo.FilesSnapshot.Files = packageFiles.ToList(); var fileSystemFiles = new List() { filePath }; - - fileSystem.Setup(x => x.get_files(It.IsAny(), It.IsAny(), SearchOption.AllDirectories)).Returns(fileSystemFiles); - filesService.Setup(x => x.get_package_file(It.IsAny())).Returns(packageFile); because(); @@ -124,14 +182,10 @@ public void should_not_delete_a_changed_file() var packageFile = new PackageFile { Path = filePath, Checksum = "1234" }; var packageFileWithUpdatedChecksum = new PackageFile { Path = filePath, Checksum = "4321" }; packageFiles.Add(packageFile); - packageInfo.FilesSnapshot.Files = packageFiles.ToList(); var fileSystemFiles = new List() { filePath }; - - fileSystem.Setup(x => x.get_files(It.IsAny(), It.IsAny(), SearchOption.AllDirectories)).Returns(fileSystemFiles); - filesService.Setup(x => x.get_package_file(It.IsAny())).Returns(packageFileWithUpdatedChecksum); because(); diff --git a/src/chocolatey/infrastructure.app/services/FilesService.cs b/src/chocolatey/infrastructure.app/services/FilesService.cs index 462e7246af..86099c88d8 100644 --- a/src/chocolatey/infrastructure.app/services/FilesService.cs +++ b/src/chocolatey/infrastructure.app/services/FilesService.cs @@ -56,7 +56,7 @@ public void save_to_file(PackageFiles snapshot, string filePath) public PackageFiles capture_package_files(PackageResult packageResult, ChocolateyConfiguration config) { - if (packageResult == null) return null; + if (packageResult == null) return new PackageFiles(); var installDirectory = packageResult.InstallLocation; if (installDirectory.is_equal_to(ApplicationParameters.InstallLocation) || installDirectory.is_equal_to(ApplicationParameters.PackagesLocation)) @@ -72,15 +72,15 @@ public PackageFiles capture_package_files(PackageResult packageResult, Chocolate public PackageFiles capture_package_files(string directory, ChocolateyConfiguration config) { + var packageFiles = new PackageFiles(); + if (directory.is_equal_to(ApplicationParameters.InstallLocation) || directory.is_equal_to(ApplicationParameters.PackagesLocation)) { var logMessage = "Install location is not specific enough, cannot capture files:{0} Erroneous install location captured as '{1}'".format_with(Environment.NewLine, directory); this.Log().Error(logMessage); - return null; + return packageFiles; } - - var packageFiles = new PackageFiles(); - + this.Log().Debug(() => "Capturing package files in '{0}'".format_with(directory)); //gather all files in the folder var files = _fileSystem.get_files(directory, pattern: "*.*", option: SearchOption.AllDirectories); diff --git a/src/chocolatey/infrastructure.app/services/NugetService.cs b/src/chocolatey/infrastructure.app/services/NugetService.cs index ffb71c025f..d19bef25ed 100644 --- a/src/chocolatey/infrastructure.app/services/NugetService.cs +++ b/src/chocolatey/infrastructure.app/services/NugetService.cs @@ -317,7 +317,7 @@ public ConcurrentDictionary install_run(ChocolateyConfigu var forcedResult = packageInstalls.GetOrAdd(packageName, new PackageResult(installedPackage, _fileSystem.combine_paths(ApplicationParameters.PackagesLocation, installedPackage.Id))); forcedResult.Messages.Add(new ResultMessage(ResultType.Note, "Backing up and removing old version")); - backup_existing_version(config, installedPackage); + backup_existing_version(config, installedPackage, _packageInfoService.get_package_information(installedPackage)); try { @@ -580,7 +580,7 @@ packages as of version 1.0.0. That is what the install command is for. version == null ? null : version.ToString())) { rename_legacy_package_version(config, installedPackage, pkgInfo); - backup_existing_version(config, installedPackage); + backup_existing_version(config, installedPackage, pkgInfo); if (config.Force && (installedPackage.Version == availablePackage.Version)) { FaultTolerance.try_catch_with_logging_exception( @@ -627,7 +627,7 @@ public void rename_legacy_package_version(ChocolateyConfiguration config, IPacka } } - public void backup_existing_version(ChocolateyConfiguration config, IPackage installedPackage) + public void backup_existing_version(ChocolateyConfiguration config, IPackage installedPackage, ChocolateyPackageInformation packageInfo) { _fileSystem.create_directory_if_not_exists(ApplicationParameters.PackageBackupLocation); @@ -672,7 +672,7 @@ public void backup_existing_version(ChocolateyConfiguration config, IPackage ins } } - backup_configuration_files(pkgInstallPath, installedPackage.Version.to_string()); + backup_changed_files(pkgInstallPath, config, packageInfo); if (errored) { @@ -685,16 +685,41 @@ process locking the folder or files. Please make sure nothing is } } - private void backup_configuration_files(string packageInstallPath, string version) + public void backup_changed_files(string packageInstallPath, ChocolateyConfiguration config, ChocolateyPackageInformation packageInfo) { - var configFiles = _fileSystem.get_files(packageInstallPath, ApplicationParameters.ConfigFileExtensions, SearchOption.AllDirectories); - foreach (var file in configFiles.or_empty_list_if_null()) + if (packageInfo == null || packageInfo.Package == null) return; + + var version = packageInfo.Package.Version.to_string(); + + if (packageInfo.FilesSnapshot == null || packageInfo.FilesSnapshot.Files.Count == 0) { - var backupName = "{0}.{1}".format_with(_fileSystem.get_file_name(file), version); + var configFiles = _fileSystem.get_files(packageInstallPath, ApplicationParameters.ConfigFileExtensions, SearchOption.AllDirectories); + foreach (var file in configFiles.or_empty_list_if_null()) + { + var backupName = "{0}.{1}".format_with(_fileSystem.get_file_name(file), version); - FaultTolerance.try_catch_with_logging_exception( - () => _fileSystem.copy_file(file, _fileSystem.combine_paths(_fileSystem.get_directory_name(file), backupName), overwriteExisting: true), - "Error backing up configuration file"); + FaultTolerance.try_catch_with_logging_exception( + () => _fileSystem.copy_file(file, _fileSystem.combine_paths(_fileSystem.get_directory_name(file), backupName), overwriteExisting: true), + "Error backing up configuration file"); + } + } + else + { + var currentFiles = _filesService.capture_package_files(packageInstallPath, config); + foreach (var currentFile in currentFiles.Files.or_empty_list_if_null()) + { + var installedFile = packageInfo.FilesSnapshot.Files.FirstOrDefault(x => x.Path.is_equal_to(currentFile.Path)); + if (installedFile != null) + { + if (!currentFile.Checksum.is_equal_to(installedFile.Checksum)) + { + var backupName = "{0}.{1}".format_with(_fileSystem.get_file_name(currentFile.Path), version); + FaultTolerance.try_catch_with_logging_exception( + () => _fileSystem.copy_file(currentFile.Path, _fileSystem.combine_paths(_fileSystem.get_directory_name(currentFile.Path), backupName), overwriteExisting: true), + "Error backing up changed file"); + } + } + } } } @@ -862,7 +887,7 @@ public ConcurrentDictionary uninstall_run(ChocolateyConfi version == null ? null : version.ToString())) { rename_legacy_package_version(config, packageVersion, pkgInfo); - backup_existing_version(config, packageVersion); + backup_existing_version(config, packageVersion, pkgInfo); packageManager.UninstallPackage(packageVersion, forceRemove: config.Force, removeDependencies: config.ForceDependencies); ensure_nupkg_is_removed(packageVersion, pkgInfo); remove_installation_files(packageVersion, pkgInfo); From ea3e73597774dc762bf0e94bce5c988ef910c258 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Fri, 15 May 2015 07:53:44 -0500 Subject: [PATCH 23/27] (maint) harden removing install files Add fault tolerance around deleting the nupkg with an error. Add fault tolerance around deleting the rest of the files on the system. --- .../infrastructure.app/services/NugetService.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/chocolatey/infrastructure.app/services/NugetService.cs b/src/chocolatey/infrastructure.app/services/NugetService.cs index d19bef25ed..b768005590 100644 --- a/src/chocolatey/infrastructure.app/services/NugetService.cs +++ b/src/chocolatey/infrastructure.app/services/NugetService.cs @@ -927,8 +927,11 @@ private void ensure_nupkg_is_removed(IPackage removedPackage, ChocolateyPackageI var nupkgFile = "{0}{1}.nupkg".format_with(removedPackage.Id, isSideBySide ? "." + removedPackage.Version.to_string() : string.Empty); var installDir = _fileSystem.combine_paths(ApplicationParameters.PackagesLocation, "{0}{1}".format_with(removedPackage.Id, isSideBySide ? "." + removedPackage.Version.to_string() : string.Empty)); var nupkg = _fileSystem.combine_paths(installDir, nupkgFile); - - _fileSystem.delete_file(nupkg); + + FaultTolerance.try_catch_with_logging_exception( + () => _fileSystem.delete_file(nupkg), + "Error deleting nupkg file", + throwError: true); } public void remove_installation_files(IPackage removedPackage, ChocolateyPackageInformation pkgInfo) @@ -943,7 +946,9 @@ public void remove_installation_files(IPackage removedPackage, ChocolateyPackage var fileSnapshot = pkgInfo.FilesSnapshot.Files.FirstOrDefault(f => f.Path.is_equal_to(file)); if (fileSnapshot != null && fileSnapshot.Checksum == _filesService.get_package_file(file).Checksum) { - _fileSystem.delete_file(file); + FaultTolerance.try_catch_with_logging_exception( + () => _fileSystem.delete_file(file), + "Error deleting file"); } } } From d028324c5903090c461721191923ffc39d2b8ae5 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Fri, 15 May 2015 08:57:18 -0500 Subject: [PATCH 24/27] (maint) Command specs for new options Added user/pass to several commands and needed to update the specs to ensure those are added to the options. --- .../commands/ChocolateyInstallCommandSpecs.cs | 25 +++++++++++++++++++ .../commands/ChocolateyListCommandSpecs.cs | 25 +++++++++++++++++++ .../commands/ChocolateyUpgradeCommandSpecs.cs | 25 +++++++++++++++++++ 3 files changed, 75 insertions(+) diff --git a/src/chocolatey.tests/infrastructure.app/commands/ChocolateyInstallCommandSpecs.cs b/src/chocolatey.tests/infrastructure.app/commands/ChocolateyInstallCommandSpecs.cs index a735d2c9a4..7cce208bb8 100644 --- a/src/chocolatey.tests/infrastructure.app/commands/ChocolateyInstallCommandSpecs.cs +++ b/src/chocolatey.tests/infrastructure.app/commands/ChocolateyInstallCommandSpecs.cs @@ -198,6 +198,31 @@ public void should_add_short_version_of_skippowershell_to_the_option_set() { optionSet.Contains("n").ShouldBeTrue(); } + + [Fact] + public void should_add_user_to_the_option_set() + { + optionSet.Contains("user").ShouldBeTrue(); + } + + [Fact] + public void should_add_short_version_of_user_to_the_option_set() + { + optionSet.Contains("u").ShouldBeTrue(); + } + + [Fact] + public void should_add_password_to_the_option_set() + { + optionSet.Contains("password").ShouldBeTrue(); + } + + [Fact] + public void should_add_short_version_of_password_to_the_option_set() + { + optionSet.Contains("p").ShouldBeTrue(); + } + } public class when_handling_additional_argument_parsing : ChocolateyInstallCommandSpecsBase diff --git a/src/chocolatey.tests/infrastructure.app/commands/ChocolateyListCommandSpecs.cs b/src/chocolatey.tests/infrastructure.app/commands/ChocolateyListCommandSpecs.cs index 0b3d198e68..b6936d68b5 100644 --- a/src/chocolatey.tests/infrastructure.app/commands/ChocolateyListCommandSpecs.cs +++ b/src/chocolatey.tests/infrastructure.app/commands/ChocolateyListCommandSpecs.cs @@ -139,6 +139,31 @@ public void should_add_short_version_of_allversions_to_the_option_set() { optionSet.Contains("a").ShouldBeTrue(); } + + [Fact] + public void should_add_user_to_the_option_set() + { + optionSet.Contains("user").ShouldBeTrue(); + } + + [Fact] + public void should_add_short_version_of_user_to_the_option_set() + { + optionSet.Contains("u").ShouldBeTrue(); + } + + [Fact] + public void should_add_password_to_the_option_set() + { + optionSet.Contains("password").ShouldBeTrue(); + } + + [Fact] + public void should_add_short_version_of_password_to_the_option_set() + { + optionSet.Contains("p").ShouldBeTrue(); + } + } public class when_handling_additional_argument_parsing : ChocolateyListCommandSpecsBase diff --git a/src/chocolatey.tests/infrastructure.app/commands/ChocolateyUpgradeCommandSpecs.cs b/src/chocolatey.tests/infrastructure.app/commands/ChocolateyUpgradeCommandSpecs.cs index 1fb5494922..a1706a3d00 100644 --- a/src/chocolatey.tests/infrastructure.app/commands/ChocolateyUpgradeCommandSpecs.cs +++ b/src/chocolatey.tests/infrastructure.app/commands/ChocolateyUpgradeCommandSpecs.cs @@ -180,6 +180,31 @@ public void should_add_short_version_of_skippowershell_to_the_option_set() { optionSet.Contains("n").ShouldBeTrue(); } + + [Fact] + public void should_add_user_to_the_option_set() + { + optionSet.Contains("user").ShouldBeTrue(); + } + + [Fact] + public void should_add_short_version_of_user_to_the_option_set() + { + optionSet.Contains("u").ShouldBeTrue(); + } + + [Fact] + public void should_add_password_to_the_option_set() + { + optionSet.Contains("password").ShouldBeTrue(); + } + + [Fact] + public void should_add_short_version_of_password_to_the_option_set() + { + optionSet.Contains("p").ShouldBeTrue(); + } + } public class when_handling_additional_argument_parsing : ChocolateyUpgradeCommandSpecsBase From 6f711add2210560728de84ef7919c2991f0dde2c Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Fri, 15 May 2015 08:58:29 -0500 Subject: [PATCH 25/27] (maint) move magic string to constants Package name of "all" is a magic string and needs to be in ApplicationParameters. --- src/chocolatey/infrastructure.app/ApplicationParameters.cs | 1 + src/chocolatey/infrastructure.app/services/NugetService.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/chocolatey/infrastructure.app/ApplicationParameters.cs b/src/chocolatey/infrastructure.app/ApplicationParameters.cs index f2a95be4d3..86e9a0ab38 100644 --- a/src/chocolatey/infrastructure.app/ApplicationParameters.cs +++ b/src/chocolatey/infrastructure.app/ApplicationParameters.cs @@ -56,6 +56,7 @@ public static class ApplicationParameters public static readonly string ChocolateyCommunityFeedPushSource = "https://chocolatey.org/"; public static readonly string UserAgent = "Chocolatey Command Line"; public static readonly string RegistryValueInstallLocation = "InstallLocation"; + public static readonly string AllPackages = "all"; /// /// Default is 45 minutes diff --git a/src/chocolatey/infrastructure.app/services/NugetService.cs b/src/chocolatey/infrastructure.app/services/NugetService.cs index b768005590..b9359f10b2 100644 --- a/src/chocolatey/infrastructure.app/services/NugetService.cs +++ b/src/chocolatey/infrastructure.app/services/NugetService.cs @@ -961,7 +961,7 @@ public void remove_installation_files(IPackage removedPackage, ChocolateyPackage private void set_package_names_if_all_is_specified(ChocolateyConfiguration config, Action customAction) { - if (config.PackageNames.is_equal_to("all")) + if (config.PackageNames.is_equal_to(ApplicationParameters.AllPackages)) { config.ListCommand.LocalOnly = true; var sources = config.Sources; From 6b0461132bf13ba5d4bab8561c815c70364938c1 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Fri, 15 May 2015 09:00:52 -0500 Subject: [PATCH 26/27] (GH-170) Add outdated command This adds the outdated command, similar to `brew outdated`. --- src/chocolatey.tests/chocolatey.tests.csproj | 1 + .../ChocolateyOutdatedCommandSpecs.cs | 142 ++++++++++++++++++ src/chocolatey/chocolatey.csproj | 1 + .../commands/ChocolateyOutdatedCommand.cs | 102 +++++++++++++ .../configuration/ChocolateyConfiguration.cs | 1 + .../domain/CommandNameType.cs | 1 + .../registration/ContainerBinding.cs | 1 + .../services/ChocolateyPackageService.cs | 42 ++++++ .../services/IChocolateyPackageService.cs | 12 ++ .../services/NugetService.cs | 35 +++-- 10 files changed, 324 insertions(+), 14 deletions(-) create mode 100644 src/chocolatey.tests/infrastructure.app/commands/ChocolateyOutdatedCommandSpecs.cs create mode 100644 src/chocolatey/infrastructure.app/commands/ChocolateyOutdatedCommand.cs diff --git a/src/chocolatey.tests/chocolatey.tests.csproj b/src/chocolatey.tests/chocolatey.tests.csproj index 0afc2017fd..9033d98df5 100644 --- a/src/chocolatey.tests/chocolatey.tests.csproj +++ b/src/chocolatey.tests/chocolatey.tests.csproj @@ -68,6 +68,7 @@ + diff --git a/src/chocolatey.tests/infrastructure.app/commands/ChocolateyOutdatedCommandSpecs.cs b/src/chocolatey.tests/infrastructure.app/commands/ChocolateyOutdatedCommandSpecs.cs new file mode 100644 index 0000000000..4a67f2dad1 --- /dev/null +++ b/src/chocolatey.tests/infrastructure.app/commands/ChocolateyOutdatedCommandSpecs.cs @@ -0,0 +1,142 @@ +// Copyright © 2011 - Present RealDimensions Software, LLC +// +// 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.tests.infrastructure.app.commands +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Moq; + using Should; + using chocolatey.infrastructure.app.attributes; + using chocolatey.infrastructure.app.commands; + using chocolatey.infrastructure.app.configuration; + using chocolatey.infrastructure.app.domain; + using chocolatey.infrastructure.app.services; + using chocolatey.infrastructure.commandline; + + public class ChocolateyOutdatedCommandSpecs + { + public abstract class ChocolateyOutdatedCommandSpecsBase : TinySpec + { + protected ChocolateyOutdatedCommand command; + protected Mock packageService = new Mock(); + protected ChocolateyConfiguration configuration = new ChocolateyConfiguration(); + + public override void Context() + { + configuration.Sources = "bob"; + command = new ChocolateyOutdatedCommand(packageService.Object); + } + } + + public class when_implementing_command_for : ChocolateyOutdatedCommandSpecsBase + { + private List results; + + public override void Because() + { + results = command.GetType().GetCustomAttributes(typeof (CommandForAttribute), false).Cast().Select(a => a.CommandName).ToList(); + } + + [Fact] + public void should_implement_outdated() + { + results.ShouldContain(CommandNameType.outdated.to_string()); + } + } + + public class when_configurating_the_argument_parser : ChocolateyOutdatedCommandSpecsBase + { + private OptionSet optionSet; + + public override void Context() + { + base.Context(); + optionSet = new OptionSet(); + } + + public override void Because() + { + command.configure_argument_parser(optionSet, configuration); + } + + [Fact] + public void should_add_source_to_the_option_set() + { + optionSet.Contains("source").ShouldBeTrue(); + } + + [Fact] + public void should_add_short_version_of_source_to_the_option_set() + { + optionSet.Contains("s").ShouldBeTrue(); + } + + [Fact] + public void should_add_user_to_the_option_set() + { + optionSet.Contains("user").ShouldBeTrue(); + } + + [Fact] + public void should_add_short_version_of_user_to_the_option_set() + { + optionSet.Contains("u").ShouldBeTrue(); + } + + [Fact] + public void should_add_password_to_the_option_set() + { + optionSet.Contains("password").ShouldBeTrue(); + } + + [Fact] + public void should_add_short_version_of_password_to_the_option_set() + { + optionSet.Contains("p").ShouldBeTrue(); + } + + } + + public class when_noop_is_called : ChocolateyOutdatedCommandSpecsBase + { + public override void Because() + { + command.noop(configuration); + } + + [Fact] + public void should_call_service_outdated_noop() + { + packageService.Verify(c => c.outdated_noop(configuration), Times.Once); + } + } + + public class when_run_is_called : ChocolateyOutdatedCommandSpecsBase + { + public override void Because() + { + command.run(configuration); + } + + [Fact] + public void should_call_service_oudated_run() + { + packageService.Verify(c => c.outdated_run(configuration), Times.Once); + } + } + } +} \ No newline at end of file diff --git a/src/chocolatey/chocolatey.csproj b/src/chocolatey/chocolatey.csproj index f75bdca93a..2daec58295 100644 --- a/src/chocolatey/chocolatey.csproj +++ b/src/chocolatey/chocolatey.csproj @@ -81,6 +81,7 @@ + diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyOutdatedCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyOutdatedCommand.cs new file mode 100644 index 0000000000..7a608c6959 --- /dev/null +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyOutdatedCommand.cs @@ -0,0 +1,102 @@ +// Copyright © 2011 - Present RealDimensions Software, LLC +// +// 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.Collections.Generic; + using attributes; + using commandline; + using configuration; + using domain; + using infrastructure.commands; + using logging; + using services; + + [CommandFor(CommandNameType.outdated)] + public sealed class ChocolateyOutdatedCommand : ICommand + { + private readonly IChocolateyPackageService _packageService; + + public ChocolateyOutdatedCommand(IChocolateyPackageService packageService) + { + _packageService = packageService; + } + + public void configure_argument_parser(OptionSet optionSet, ChocolateyConfiguration configuration) + { + optionSet + .Add("s=|source=", + "Source - The source to find the package(s) to install. Special sources include: ruby, webpi, cygwin, windowsfeatures, and python. Defaults to default feeds.", + option => configuration.Sources = option.remove_surrounding_quotes()) + .Add("u=|user=", + "User - used with authenticated feeds. Defaults to empty.", + option => configuration.SourceCommand.Username = option.remove_surrounding_quotes()) + .Add("p=|password=", + "Password - the user's password to the source. Defaults to empty.", + option => configuration.SourceCommand.Password = option.remove_surrounding_quotes()) + ; + } + + public void handle_additional_argument_parsing(IList unparsedArguments, ChocolateyConfiguration configuration) + { + configuration.Input = string.Join(" ", unparsedArguments); + configuration.PackageNames = string.Join(ApplicationParameters.PackageNamesSeparator.to_string(), unparsedArguments); + } + + public void handle_validation(ChocolateyConfiguration configuration) + { + } + + public void help_message(ChocolateyConfiguration configuration) + { + this.Log().Info(ChocolateyLoggers.Important, "Outdated Command"); + this.Log().Info(@" +Returns a list of outdated packages +"); + + "chocolatey".Log().Info(ChocolateyLoggers.Important, "Usage"); + "chocolatey".Log().Info(@" + choco outdated [] +"); + + "chocolatey".Log().Info(ChocolateyLoggers.Important, "Examples"); + "chocolatey".Log().Info(@" + choco outdated + choco outdated -s ""https://somewhere/out/there"" + choco outdated -s ""https://somewhere/protected"" -u user -p pass + +If you use `--source=https://somewhere/out/there`, it is + going to look for outdated packages only based on that source. +"); + + "chocolatey".Log().Info(ChocolateyLoggers.Important, "Options and Switches"); + } + + public void noop(ChocolateyConfiguration configuration) + { + _packageService.outdated_noop(configuration); + } + + public void run(ChocolateyConfiguration configuration) + { + _packageService.outdated_run(configuration); + } + + public bool may_require_admin_access() + { + return false; + } + } +} \ No newline at end of file diff --git a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs index dadd8b4322..7b6abd6984 100644 --- a/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs +++ b/src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs @@ -313,6 +313,7 @@ public sealed class ListCommandConfiguration public sealed class UpgradeCommandConfiguration { public bool FailOnUnfound { get; set; } + public bool NotifyOnlyAvailableUpgrades { get; set; } } [Serializable] diff --git a/src/chocolatey/infrastructure.app/domain/CommandNameType.cs b/src/chocolatey/infrastructure.app/domain/CommandNameType.cs index 334ad2f4c3..d2e54a032f 100644 --- a/src/chocolatey/infrastructure.app/domain/CommandNameType.cs +++ b/src/chocolatey/infrastructure.app/domain/CommandNameType.cs @@ -27,6 +27,7 @@ public enum CommandNameType //[Description("update - updates package index")] update, [Description("update - [DEPRECATED] RESERVED for future use (you are looking for upgrade, these are not the droids you are looking for)")] update, [Description("upgrade - upgrades packages from various sources")] upgrade, + [Description("outdated - retrieves packages that are outdated. Similar to upgrade all --noop")] outdated, [Description("uninstall - uninstalls a package")] uninstall, [Description("source - view and configure default sources")] source, [Description("sources - view and configure default sources (alias for source)")] diff --git a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs index 794b3463b5..fd35fb716a 100644 --- a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs +++ b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs @@ -72,6 +72,7 @@ public void RegisterComponents(Container container) new ChocolateyListCommand(container.GetInstance()), new ChocolateyInstallCommand(container.GetInstance()), new ChocolateyPinCommand(container.GetInstance(), container.GetInstance(), container.GetInstance()), + new ChocolateyOutdatedCommand(container.GetInstance()), new ChocolateyUpgradeCommand(container.GetInstance()), new ChocolateyUninstallCommand(container.GetInstance()), new ChocolateyPackCommand(container.GetInstance()), diff --git a/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs b/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs index aa8db5f3ba..2cddc469d6 100644 --- a/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs +++ b/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs @@ -284,6 +284,48 @@ public ConcurrentDictionary install_run(ChocolateyConfigu return packageInstalls; } + public void outdated_noop(ChocolateyConfiguration config) + { + this.Log().Info(@" +Would have determined packages that are out of date based on what is + installed and what versions are available for upgrade."); + } + + public void outdated_run(ChocolateyConfiguration config) + { + this.Log().Info(ChocolateyLoggers.Important, @"Outdated Packages + Output is package name | current version | available version | pinned? +"); + + config.PackageNames = ApplicationParameters.AllPackages; + config.UpgradeCommand.NotifyOnlyAvailableUpgrades = true; + + var output = config.RegularOutput; + config.RegularOutput = false; + var oudatedPackages = _nugetService.upgrade_noop(config, null); + config.RegularOutput = output; + + if (config.RegularOutput) + { + var upgradeWarnings = oudatedPackages.Count(p => p.Value.Warning); + this.Log().Warn(() => @"{0}{1} has determined {2} package(s) are outdated. {3}.".format_with( + Environment.NewLine, + ApplicationParameters.Name, + oudatedPackages.Count(p => p.Value.Success && !p.Value.Inconclusive), + upgradeWarnings == 0 ? string.Empty : "{0} {1} package(s) had warnings.".format_with(Environment.NewLine, upgradeWarnings) + )); + + if (upgradeWarnings != 0) + { + this.Log().Warn(ChocolateyLoggers.Important, "Warnings:"); + foreach (var warning in oudatedPackages.Where(p => p.Value.Warning).or_empty_list_if_null()) + { + this.Log().Warn(ChocolateyLoggers.Important, " - {0}".format_with(warning.Value.Name)); + } + } + } + } + private IEnumerable set_config_from_package_names_and_packages_config(ChocolateyConfiguration config, ConcurrentDictionary packageInstalls) { // if there are any .config files, split those off of the config. Then return the config without those package names. diff --git a/src/chocolatey/infrastructure.app/services/IChocolateyPackageService.cs b/src/chocolatey/infrastructure.app/services/IChocolateyPackageService.cs index 7e468977a5..45b0e2f206 100644 --- a/src/chocolatey/infrastructure.app/services/IChocolateyPackageService.cs +++ b/src/chocolatey/infrastructure.app/services/IChocolateyPackageService.cs @@ -75,6 +75,18 @@ public interface IChocolateyPackageService /// results of installs ConcurrentDictionary install_run(ChocolateyConfiguration config); + /// + /// Run outdated in noop mode + /// + /// The configuration. + void outdated_noop(ChocolateyConfiguration config); + + /// + /// Determines all packages that are out of date + /// + /// The configuration. + void outdated_run(ChocolateyConfiguration config); + /// /// Run upgrade in noop mode /// diff --git a/src/chocolatey/infrastructure.app/services/NugetService.cs b/src/chocolatey/infrastructure.app/services/NugetService.cs index b9359f10b2..1c2ccc30f5 100644 --- a/src/chocolatey/infrastructure.app/services/NugetService.cs +++ b/src/chocolatey/infrastructure.app/services/NugetService.cs @@ -505,14 +505,18 @@ packages as of version 1.0.0. That is what the install command is for. string logMessage = "{0} v{1} is newer than the most recent.{2} You must be smarter than the average bear...".format_with(installedPackage.Id, installedPackage.Version, Environment.NewLine); packageResult.Messages.Add(new ResultMessage(ResultType.Inconclusive, logMessage)); - if (config.RegularOutput) - { - this.Log().Info(ChocolateyLoggers.Important, logMessage); - } - else + if (!config.UpgradeCommand.NotifyOnlyAvailableUpgrades) { - this.Log().Info("{0}|{1}|{1}|{2}".format_with(installedPackage.Id, installedPackage.Version, isPinned.to_string().to_lower())); + if (config.RegularOutput) + { + this.Log().Info(ChocolateyLoggers.Important, logMessage); + } + else + { + this.Log().Info("{0}|{1}|{1}|{2}".format_with(installedPackage.Id, installedPackage.Version, isPinned.to_string().to_lower())); + } } + continue; } @@ -527,15 +531,18 @@ packages as of version 1.0.0. That is what the install command is for. packageResult.Messages.Add(new ResultMessage(ResultType.Inconclusive, logMessage)); } - if (config.RegularOutput) - { - this.Log().Info(logMessage); - } - else + if (!config.UpgradeCommand.NotifyOnlyAvailableUpgrades) { - this.Log().Info("{0}|{1}|{2}|{3}".format_with(installedPackage.Id, installedPackage.Version, availablePackage.Version, isPinned.to_string().to_lower())); + if (config.RegularOutput) + { + this.Log().Info(logMessage); + } + else + { + this.Log().Info("{0}|{1}|{2}|{3}".format_with(installedPackage.Id, installedPackage.Version, availablePackage.Version, isPinned.to_string().to_lower())); + } } - + continue; } @@ -547,7 +554,7 @@ packages as of version 1.0.0. That is what the install command is for. { if (availablePackage.Version > installedPackage.Version) { - string logMessage = "You have {0} v{1} installed. Version {2} is available based on your source(s)".format_with(installedPackage.Id, installedPackage.Version, availablePackage.Version); + string logMessage = "You have {0} v{1} installed. Version {2} is available based on your source(s).".format_with(installedPackage.Id, installedPackage.Version, availablePackage.Version); packageResult.Messages.Add(new ResultMessage(ResultType.Note, logMessage)); if (config.RegularOutput) From 9c1bfe1d30e958e4113289bafbd8b1954ad8c3f8 Mon Sep 17 00:00:00 2001 From: Rob Reynolds Date: Fri, 15 May 2015 09:01:01 -0500 Subject: [PATCH 27/27] (maint) formatting --- .../infrastructure.app/commands/ChocolateyUpgradeCommand.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs b/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs index db792153f7..cb20d72458 100644 --- a/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs +++ b/src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs @@ -73,7 +73,7 @@ public virtual void configure_argument_parser(OptionSet optionSet, ChocolateyCon .Add("n|skippowershell|skip-powershell", "Skip Powershell - Do not run chocolateyInstall.ps1. Defaults to false.", option => configuration.SkipPackageInstallProvider = option != null) - .Add("failonunfound|fail-on-unfound", + .Add("failonunfound|fail-on-unfound", "Fail On Unfound Packages - If a package is not found in feeds specified, fail instead of warn.", option => configuration.UpgradeCommand.FailOnUnfound = option != null) .Add("u=|user=",