Ejemplo n.º 1
0
        public static async Task <bool> InstallFromSourceAsync(
            PackageIdentity packageIdentity,
            IPackageDownloader packageDownloader,
            VersionFolderPathResolver versionFolderPathResolver,
            PackageExtractionContext packageExtractionContext,
            CancellationToken token,
            Guid parentId = default(Guid))
        {
            if (packageDownloader == null)
            {
                throw new ArgumentNullException(nameof(packageDownloader));
            }

            if (packageExtractionContext == null)
            {
                throw new ArgumentNullException(nameof(packageExtractionContext));
            }

            var logger = packageExtractionContext.Logger;

            using (var telemetry = TelemetryActivity.CreateTelemetryActivityWithNewOperationId(parentId))
            {
                var targetPath   = versionFolderPathResolver.GetInstallPath(packageIdentity.Id, packageIdentity.Version);
                var targetNuspec = versionFolderPathResolver.GetManifestFilePath(packageIdentity.Id, packageIdentity.Version);
                var targetNupkg  = versionFolderPathResolver.GetPackageFilePath(packageIdentity.Id, packageIdentity.Version);
                var hashPath     = versionFolderPathResolver.GetHashPath(packageIdentity.Id, packageIdentity.Version);

                logger.LogVerbose(
                    $"Acquiring lock for the installation of {packageIdentity.Id} {packageIdentity.Version}");

                // Acquire the lock on a nukpg before we extract it to prevent the race condition when multiple
                // processes are extracting to the same destination simultaneously
                return(await ConcurrencyUtilities.ExecuteWithFileLockedAsync(targetNupkg,
                                                                             action : async cancellationToken =>
                {
                    // If this is the first process trying to install the target nupkg, go ahead
                    // After this process successfully installs the package, all other processes
                    // waiting on this lock don't need to install it again.
                    if (!File.Exists(hashPath))
                    {
                        logger.LogVerbose(
                            $"Acquired lock for the installation of {packageIdentity.Id} {packageIdentity.Version}");

                        logger.LogMinimal(string.Format(
                                              CultureInfo.CurrentCulture,
                                              Strings.Log_InstallingPackage,
                                              packageIdentity.Id,
                                              packageIdentity.Version));

                        cancellationToken.ThrowIfCancellationRequested();

                        // We do not stop the package extraction after this point
                        // based on CancellationToken, but things can still be stopped if the process is killed.
                        if (Directory.Exists(targetPath))
                        {
                            // If we had a broken restore, clean out the files first
                            var info = new DirectoryInfo(targetPath);

                            foreach (var file in info.GetFiles())
                            {
                                file.Delete();
                            }

                            foreach (var dir in info.GetDirectories())
                            {
                                dir.Delete(true);
                            }
                        }
                        else
                        {
                            Directory.CreateDirectory(targetPath);
                        }

                        var targetTempNupkg = Path.Combine(targetPath, Path.GetRandomFileName());
                        var tempHashPath = Path.Combine(targetPath, Path.GetRandomFileName());
                        var packageSaveMode = packageExtractionContext.PackageSaveMode;

                        // Extract the nupkg
                        var copiedNupkg = await packageDownloader.CopyNupkgFileToAsync(targetTempNupkg, cancellationToken);

                        if (packageSaveMode.HasFlag(PackageSaveMode.Nuspec) || packageSaveMode.HasFlag(PackageSaveMode.Files))
                        {
                            try
                            {
                                telemetry.StartIntervalMeasure();

                                await VerifyPackageSignatureAsync(
                                    packageDownloader.Source,
                                    telemetry.OperationId,
                                    packageIdentity,
                                    packageExtractionContext,
                                    packageDownloader.SignedPackageReader,
                                    token);

                                telemetry.EndIntervalMeasure(PackagingConstants.PackageVerifyDurationName);
                            }
                            catch (SignatureException)
                            {
                                try
                                {
                                    // Dispose of it now because it is holding a lock on the temporary .nupkg file.
                                    packageDownloader.Dispose();

                                    DeleteTargetAndTempPaths(targetPath, targetTempNupkg);
                                }
                                catch (IOException ex)
                                {
                                    logger.LogWarning(string.Format(
                                                          CultureInfo.CurrentCulture,
                                                          Strings.ErrorUnableToDeleteFile,
                                                          targetTempNupkg,
                                                          ex.Message));
                                }

                                telemetry.TelemetryEvent = new PackageExtractionTelemetryEvent(
                                    packageExtractionContext.PackageSaveMode,
                                    NuGetOperationStatus.Failed,
                                    ExtractionSource.RestoreCommand,
                                    packageIdentity);
                                throw;
                            }
                        }

                        if (packageSaveMode.HasFlag(PackageSaveMode.Nuspec))
                        {
                            var nuspecFileNameFromReader = await packageDownloader.CoreReader.GetNuspecFileAsync(cancellationToken);
                            var packageFiles = new[] { nuspecFileNameFromReader };
                            var packageFileExtractor = new PackageFileExtractor(
                                packageFiles,
                                XmlDocFileSaveMode.None);
                            var packageDirectoryPath = Path.GetDirectoryName(targetNuspec);

                            var extractedNuspecFilePath = (await packageDownloader.CoreReader.CopyFilesAsync(
                                                               packageDirectoryPath,
                                                               packageFiles,
                                                               packageFileExtractor.ExtractPackageFile,
                                                               logger,
                                                               cancellationToken))
                                                          .SingleOrDefault();

                            // CopyFilesAsync(...) just extracts files to a directory.
                            // We may have to fix up the casing of the .nuspec file name.
                            if (!string.IsNullOrEmpty(extractedNuspecFilePath))
                            {
                                if (PathUtility.IsFileSystemCaseInsensitive)
                                {
                                    var nuspecFileName = Path.GetFileName(targetNuspec);
                                    var actualNuspecFileName = Path.GetFileName(extractedNuspecFilePath);

                                    if (!string.Equals(nuspecFileName, actualNuspecFileName, StringComparison.Ordinal))
                                    {
                                        var tempNuspecFilePath = Path.Combine(packageDirectoryPath, Path.GetRandomFileName());

                                        File.Move(extractedNuspecFilePath, tempNuspecFilePath);
                                        File.Move(tempNuspecFilePath, targetNuspec);
                                    }
                                }
                                else if (!File.Exists(targetNuspec))
                                {
                                    File.Move(extractedNuspecFilePath, targetNuspec);
                                }
                            }
                        }

                        if (packageSaveMode.HasFlag(PackageSaveMode.Files))
                        {
                            var hashFileName = Path.GetFileName(hashPath);
                            var packageFiles = (await packageDownloader.CoreReader.GetFilesAsync(cancellationToken))
                                               .Where(file => ShouldInclude(file, hashFileName));
                            var packageFileExtractor = new PackageFileExtractor(
                                packageFiles,
                                packageExtractionContext.XmlDocFileSaveMode);
                            await packageDownloader.CoreReader.CopyFilesAsync(
                                targetPath,
                                packageFiles,
                                packageFileExtractor.ExtractPackageFile,
                                logger,
                                token);
                        }

                        var packageHash = await packageDownloader.GetPackageHashAsync("SHA512", cancellationToken);

                        File.WriteAllText(tempHashPath, packageHash);

                        // Now rename the tmp file
                        if (packageExtractionContext.PackageSaveMode.HasFlag(PackageSaveMode.Nupkg))
                        {
                            if (copiedNupkg)
                            {
                                // Dispose of it now because it is holding a lock on the temporary .nupkg file.
                                packageDownloader.Dispose();

                                File.Move(targetTempNupkg, targetNupkg);
                            }
                        }
                        else
                        {
                            try
                            {
                                File.Delete(targetTempNupkg);
                            }
                            catch (IOException ex)
                            {
                                logger.LogWarning(string.Format(
                                                      CultureInfo.CurrentCulture,
                                                      Strings.ErrorUnableToDeleteFile,
                                                      targetTempNupkg,
                                                      ex.Message));
                            }
                        }

                        // Note: PackageRepository relies on the hash file being written out as the
                        // final operation as part of a package install to assume a package was fully installed.
                        // Rename the tmp hash file
                        File.Move(tempHashPath, hashPath);

                        logger.LogVerbose($"Completed installation of {packageIdentity.Id} {packageIdentity.Version}");

                        telemetry.TelemetryEvent = new PackageExtractionTelemetryEvent(
                            packageExtractionContext.PackageSaveMode,
                            NuGetOperationStatus.Succeeded,
                            ExtractionSource.RestoreCommand,
                            packageIdentity);
                        return true;
                    }
                    else
                    {
                        logger.LogVerbose("Lock not required - Package already installed "
                                          + $"{packageIdentity.Id} {packageIdentity.Version}");

                        telemetry.TelemetryEvent = new PackageExtractionTelemetryEvent(
                            packageExtractionContext.PackageSaveMode,
                            NuGetOperationStatus.NoOp,
                            ExtractionSource.RestoreCommand,
                            packageIdentity);
                        return false;
                    }
                },
                                                                             token : token));
            }
        }