in src/NuGet.Core/NuGet.Packaging/PackageExtractor.cs [638:878]
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;
var packageExtractionTelemetryEvent = new PackageExtractionTelemetryEvent(packageExtractionContext.PackageSaveMode, NuGetOperationStatus.Failed, ExtractionSource.RestoreCommand, packageIdentity);
using (var telemetry = TelemetryActivity.Create(parentId, packageExtractionTelemetryEvent))
{
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);
var nupkgMetadataFilePath = versionFolderPathResolver.GetNupkgMetadataPath(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(nupkgMetadataFilePath))
{
logger.LogVerbose(
$"Acquired lock for the installation of {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 tempNupkgMetadataFilePath = 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));
}
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 nupkgMetadataFileName = Path.GetFileName(nupkgMetadataFilePath);
var packageFiles = (await packageDownloader.CoreReader.GetFilesAsync(cancellationToken))
.Where(file => ShouldInclude(file, hashFileName, nupkgMetadataFileName));
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);
// get hash for the unsigned content of package
var contentHash = packageDownloader.SignedPackageReader.GetContentHash(cancellationToken, GetUnsignedPackageHash: () => packageHash);
// write the new hash file
var hashFile = new NupkgMetadataFile()
{
ContentHash = contentHash,
Source = packageDownloader.Source
};
NupkgMetadataFileFormat.Write(tempNupkgMetadataFilePath, hashFile);
// 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);
File.Move(tempNupkgMetadataFilePath, nupkgMetadataFilePath);
logger.LogInformation(StringFormatter.Log_InstalledPackage(packageIdentity.Id, packageIdentity.Version.OriginalVersion, packageDownloader.Source, contentHash, targetPath));
packageExtractionTelemetryEvent.SetResult(NuGetOperationStatus.Succeeded);
return true;
}
else
{
logger.LogVerbose("Lock not required - Package already installed "
+ $"{packageIdentity.Id} {packageIdentity.Version}");
packageExtractionTelemetryEvent.SetResult(NuGetOperationStatus.NoOp);
return false;
}
},
token: token);
}
}