public static async Task InstallFromSourceAsync( Func <Stream, Task> copyToAsync, VersionFolderPathContext versionFolderPathContext, CancellationToken token) { if (copyToAsync == null) { throw new ArgumentNullException(nameof(copyToAsync)); } if (versionFolderPathContext == null) { throw new ArgumentNullException(nameof(versionFolderPathContext)); } var packagePathResolver = new VersionFolderPathResolver(versionFolderPathContext.PackagesDirectory); var packageIdentity = versionFolderPathContext.Package; var logger = versionFolderPathContext.Logger; var targetPath = packagePathResolver.GetInstallPath(packageIdentity.Id, packageIdentity.Version); var targetNuspec = packagePathResolver.GetManifestFilePath(packageIdentity.Id, packageIdentity.Version); var targetNupkg = packagePathResolver.GetPackageFilePath(packageIdentity.Id, packageIdentity.Version); var hashPath = packagePathResolver.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 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 = versionFolderPathContext.PackageSaveMode; // Extract the nupkg using (var nupkgStream = new FileStream( targetTempNupkg, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite | FileShare.Delete, bufferSize: 4096, useAsync: true)) { await copyToAsync(nupkgStream); nupkgStream.Seek(0, SeekOrigin.Begin); using (var packageReader = new PackageArchiveReader(nupkgStream)) { var nuspecFile = packageReader.GetNuspecFile(); if ((packageSaveMode & PackageSaveMode.Nuspec) == PackageSaveMode.Nuspec) { packageReader.ExtractFile(nuspecFile, targetNuspec, logger); } if ((packageSaveMode & PackageSaveMode.Files) == PackageSaveMode.Files) { var nupkgFileName = Path.GetFileName(targetNupkg); var nuspecFileName = Path.GetFileName(targetNuspec); var hashFileName = Path.GetFileName(hashPath); var packageFiles = packageReader.GetFiles() .Where(file => ShouldInclude(file, hashFileName)); var packageFileExtractor = new PackageFileExtractor( packageFiles, versionFolderPathContext.XmlDocFileSaveMode); packageReader.CopyFiles( targetPath, packageFiles, packageFileExtractor.ExtractPackageFile, logger, token); } string packageHash; nupkgStream.Position = 0; packageHash = Convert.ToBase64String(new CryptoHashProvider("SHA512").CalculateHash(nupkgStream)); File.WriteAllText(tempHashPath, packageHash); } } // Now rename the tmp file if ((versionFolderPathContext.PackageSaveMode & PackageSaveMode.Nupkg) == PackageSaveMode.Nupkg) { 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}"); } else { logger.LogVerbose("Lock not required - Package already installed " + $"{packageIdentity.Id} {packageIdentity.Version}"); } return(0); }, token : token); }
public static async Task <bool> InstallFromSourceAsync( IPackageDownloader packageDownloader, VersionFolderPathContext versionFolderPathContext, CancellationToken token) { if (packageDownloader == null) { throw new ArgumentNullException(nameof(packageDownloader)); } if (versionFolderPathContext == null) { throw new ArgumentNullException(nameof(versionFolderPathContext)); } var versionFolderPathResolver = new VersionFolderPathResolver( versionFolderPathContext.PackagesDirectory, versionFolderPathContext.IsLowercasePackagesDirectory); var packageIdentity = versionFolderPathContext.Package; var logger = versionFolderPathContext.Logger; 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 = versionFolderPathContext.PackageSaveMode; // Extract the nupkg var copiedNupkg = await packageDownloader.CopyNupkgFileToAsync(targetTempNupkg, cancellationToken); 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, versionFolderPathContext.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 (versionFolderPathContext.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}"); return true; } else { logger.LogVerbose("Lock not required - Package already installed " + $"{packageIdentity.Id} {packageIdentity.Version}"); return false; } }, token : token)); }