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/chocolatey.tests.integration.csproj b/src/chocolatey.tests.integration/chocolatey.tests.integration.csproj index 52fd7c9a05..74e6aa82d5 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/context/installpackage/installpackage.1.0.0.nupkg b/src/chocolatey.tests.integration/context/installpackage/installpackage.1.0.0.nupkg index 749cb1a648..277f38fee2 100644 Binary files a/src/chocolatey.tests.integration/context/installpackage/installpackage.1.0.0.nupkg and b/src/chocolatey.tests.integration/context/installpackage/installpackage.1.0.0.nupkg differ 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/cryptography/CrytpoHashProviderSpecs.cs b/src/chocolatey.tests.integration/infrastructure/cryptography/CrytpoHashProviderSpecs.cs new file mode 100644 index 0000000000..20aa0947e7 --- /dev/null +++ b/src/chocolatey.tests.integration/infrastructure/cryptography/CrytpoHashProviderSpecs.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 CrytpoHashProviderSpecs + { + public abstract class CrytpoHashProviderSpecsBase : TinySpec + { + protected CrytpoHashProvider Provider; + protected DotNetFileSystem FileSystem; + protected string ContextDirectory; + + public override void Context() + { + FileSystem = new DotNetFileSystem(); + 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_HashProvider_provides_a_hash : CrytpoHashProviderSpecsBase + { + 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.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.tests/chocolatey.tests.csproj b/src/chocolatey.tests/chocolatey.tests.csproj index f31a59eecf..0afc2017fd 100644 --- a/src/chocolatey.tests/chocolatey.tests.csproj +++ b/src/chocolatey.tests/chocolatey.tests.csproj @@ -77,11 +77,14 @@ + + + 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..5f4b471d9b --- /dev/null +++ b/src/chocolatey.tests/infrastructure.app/services/FilesServiceSpecs.cs @@ -0,0 +1,254 @@ +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_a_non_null_object() + { + result.ShouldNotBeNull(); + } + + [Fact] + public void should_return_empty_package_files() + { + result.Files.ShouldBeEmpty(); + } + } + + 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.app/services/NugetServiceSpecs.cs b/src/chocolatey.tests/infrastructure.app/services/NugetServiceSpecs.cs new file mode 100644 index 0000000000..1f89ade817 --- /dev/null +++ b/src/chocolatey.tests/infrastructure.app/services/NugetServiceSpecs.cs @@ -0,0 +1,220 @@ +// 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.configuration; + 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_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; + 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.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/chocolatey.csproj b/src/chocolatey/chocolatey.csproj index 87f6acf0ec..f75bdca93a 100644 --- a/src/chocolatey/chocolatey.csproj +++ b/src/chocolatey/chocolatey.csproj @@ -94,6 +94,8 @@ + + @@ -102,6 +104,8 @@ + + @@ -162,6 +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/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; } 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..f7f53b20d1 --- /dev/null +++ b/src/chocolatey/infrastructure.app/domain/PackageFiles.cs @@ -0,0 +1,60 @@ +// 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; } + + } +} \ No newline at end of file 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; } } diff --git a/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs b/src/chocolatey/infrastructure.app/registration/ContainerBinding.cs index 995a09b297..794b3463b5 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 @@ -53,6 +55,8 @@ 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/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); 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 new file mode 100644 index 0000000000..86099c88d8 --- /dev/null +++ b/src/chocolatey/infrastructure.app/services/FilesService.cs @@ -0,0 +1,104 @@ +// 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 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, IHashProvider hashProvider) + { + _xmlService = xmlService; + _fileSystem = fileSystem; + _hashProvider = hashProvider; + } + + 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) + { + if (snapshot == null) return; + + _xmlService.serialize(snapshot, filePath); + } + + public PackageFiles capture_package_files(PackageResult packageResult, ChocolateyConfiguration config) + { + if (packageResult == null) return new PackageFiles(); + + 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; + } + + return capture_package_files(installDirectory, config); + } + + 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 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); + foreach (string file in files.or_empty_list_if_null()) + { + 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 new file mode 100644 index 0000000000..09687a0eaf --- /dev/null +++ b/src/chocolatey/infrastructure.app/services/IFilesService.cs @@ -0,0 +1,64 @@ +// 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 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); + + /// + /// 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 + /// + /// 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..b768005590 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) @@ -265,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); @@ -286,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; } @@ -304,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); + backup_existing_version(config, installedPackage, _packageInfoService.get_package_information(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)); } } @@ -347,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); } } @@ -400,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); @@ -423,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; @@ -447,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); } } @@ -465,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); @@ -495,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) { @@ -519,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) @@ -536,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); } @@ -545,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) { @@ -560,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; @@ -577,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( @@ -595,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); } } } @@ -624,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); @@ -669,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) { @@ -682,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"); + } + } + } } } @@ -730,11 +758,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; } @@ -754,8 +783,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 { @@ -789,8 +818,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; @@ -841,9 +870,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; } @@ -858,9 +887,10 @@ 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); } } catch (Exception ex) @@ -874,6 +904,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); } @@ -896,8 +927,36 @@ 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) + { + 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) + { + FaultTolerance.try_catch_with_logging_exception( + () => _fileSystem.delete_file(file), + "Error deleting file"); + } + } + } + + 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) 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/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/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