/// <summary> /// Asynchronously gets dependency information for a specific package. /// </summary> /// <param name="id">A package id.</param> /// <param name="version">A package version.</param> /// <param name="cacheContext">A source cache context.</param> /// <param name="logger">A logger.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <returns>A task that represents the asynchronous operation. /// The task result (<see cref="Task{TResult}.Result" />) returns an /// <see cref="IEnumerable{NuGetVersion}" />.</returns> /// <exception cref="ArgumentException">Thrown if <paramref name="id" /> /// is either <c>null</c> or an empty string.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="version" /> <c>null</c>.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="cacheContext" /> <c>null</c>.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="logger" /> <c>null</c>.</exception> /// <exception cref="OperationCanceledException">Thrown if <paramref name="cancellationToken" /> /// is cancelled.</exception> public override async Task <FindPackageByIdDependencyInfo> GetDependencyInfoAsync( string id, NuGetVersion version, SourceCacheContext cacheContext, ILogger logger, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(id)) { throw new ArgumentException(Strings.ArgumentCannotBeNullOrEmpty, nameof(id)); } if (version == null) { throw new ArgumentNullException(nameof(version)); } if (cacheContext == null) { throw new ArgumentNullException(nameof(cacheContext)); } if (logger == null) { throw new ArgumentNullException(nameof(logger)); } var stopwatch = Stopwatch.StartNew(); try { cancellationToken.ThrowIfCancellationRequested(); var packageInfo = await GetPackageInfoAsync(id, version, cacheContext, logger, cancellationToken); if (packageInfo == null) { logger.LogWarning($"Unable to find package {id}{version}"); return(null); } var reader = await _nupkgDownloader.GetNuspecReaderFromNupkgAsync( packageInfo.Identity, packageInfo.ContentUri, cacheContext, logger, cancellationToken); return(GetDependencyInfo(reader)); } finally { ProtocolDiagnostics.RaiseEvent(new ProtocolDiagnosticResourceEvent( PackageSource.Source, ResourceTypeName, ThisTypeName, nameof(GetDependencyInfoAsync), stopwatch.Elapsed)); } }
/// <summary> /// Asynchronously copies a .nupkg to a target file path. /// </summary> /// <param name="destinationFilePath">The destination file path.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <returns>A task that represents the asynchronous operation. /// The task result (<see cref="Task{TResult}.Result" />) returns a <see cref="bool" /> /// indicating whether or not the copy was successful.</returns> /// <exception cref="ObjectDisposedException">Thrown if this object is disposed.</exception> /// <exception cref="ArgumentException">Thrown if <paramref name="destinationFilePath" /> /// is either <c>null</c> or empty.</exception> /// <exception cref="OperationCanceledException">Thrown if <paramref name="cancellationToken" /> /// is cancelled.</exception> public async Task <bool> CopyNupkgFileToAsync(string destinationFilePath, CancellationToken cancellationToken) { ThrowIfDisposed(); if (string.IsNullOrEmpty(destinationFilePath)) { throw new ArgumentException( string.Format( CultureInfo.CurrentCulture, Strings.StringCannotBeNullOrEmpty, nameof(destinationFilePath)), nameof(destinationFilePath)); } try { if (_throttle != null) { await _throttle.WaitAsync(); } cancellationToken.ThrowIfCancellationRequested(); using (var source = File.OpenRead(_packageFilePath)) using (var destination = new FileStream( destinationFilePath, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite | FileShare.Delete, bufferSize: 4096)) { // This value comes from NuGet.Protocol.StreamExtensions.CopyToAsync(...). // While 8K may or may not be the optimal buffer size for copy performance, // it is better than 4K. const int bufferSize = 8192; await source.CopyToAsync(destination, bufferSize, cancellationToken); ProtocolDiagnostics.RaiseEvent(new ProtocolDiagnosticNupkgCopiedEvent(Source, destination.Length)); return(true); } } catch (Exception ex) { if (!await _handleExceptionAsync(ex)) { throw; } } finally { _throttle?.Release(); } return(false); }
public override Task <DownloadResourceResult> GetDownloadResourceResultAsync( PackageIdentity identity, PackageDownloadContext downloadContext, string globalPackagesFolder, ILogger logger, CancellationToken token) { if (identity == null) { throw new ArgumentNullException(nameof(identity)); } if (logger == null) { throw new ArgumentNullException(nameof(logger)); } var stopwatch = Stopwatch.StartNew(); try { // Find the package from the local folder LocalPackageInfo packageInfo = null; var sourcePackage = identity as SourcePackageDependencyInfo; if (sourcePackage?.DownloadUri != null) { // Get the package directly if the full path is known packageInfo = _localResource.GetPackage(sourcePackage.DownloadUri, logger, token); } else { // Search for the local package packageInfo = _localResource.GetPackage(identity, logger, token); } if (packageInfo != null) { var stream = File.OpenRead(packageInfo.Path); return(Task.FromResult(new DownloadResourceResult(stream, packageInfo.GetReader(), _localResource.Root))); } else { return(Task.FromResult(new DownloadResourceResult(DownloadResourceResultStatus.NotFound))); } } finally { ProtocolDiagnostics.RaiseEvent(new ProtocolDiagnosticResourceEvent( _source, resourceType: nameof(DownloadResource), type: nameof(LocalDownloadResource), method: nameof(GetDownloadResourceResultAsync), stopwatch.Elapsed)); } }
/// <summary> /// Asynchronously gets dependency information for a specific package. /// </summary> /// <param name="id">A package id.</param> /// <param name="version">A package version.</param> /// <param name="cacheContext">A source cache context.</param> /// <param name="logger">A logger.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <returns>A task that represents the asynchronous operation. /// The task result (<see cref="Task{TResult}.Result" />) returns an /// <see cref="IEnumerable{NuGetVersion}" />.</returns> /// <exception cref="ArgumentException">Thrown if <paramref name="id" /> /// is either <c>null</c> or an empty string.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="version" /> <c>null</c>.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="cacheContext" /> <c>null</c>.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="logger" /> <c>null</c>.</exception> /// <exception cref="OperationCanceledException">Thrown if <paramref name="cancellationToken" /> /// is cancelled.</exception> public override Task <FindPackageByIdDependencyInfo> GetDependencyInfoAsync( string id, NuGetVersion version, SourceCacheContext cacheContext, ILogger logger, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(id)) { throw new ArgumentException(Strings.ArgumentCannotBeNullOrEmpty, nameof(id)); } if (version == null) { throw new ArgumentNullException(nameof(version)); } if (cacheContext == null) { throw new ArgumentNullException(nameof(cacheContext)); } if (logger == null) { throw new ArgumentNullException(nameof(logger)); } var stopwatch = Stopwatch.StartNew(); try { cancellationToken.ThrowIfCancellationRequested(); FindPackageByIdDependencyInfo dependencyInfo = null; var info = GetPackageInfo(id, version, cacheContext, logger); if (info != null) { dependencyInfo = GetDependencyInfo(info.Nuspec); } return(Task.FromResult(dependencyInfo)); } finally { ProtocolDiagnostics.RaiseEvent(new ProtocolDiagnosticResourceEvent( _source, ResourceTypeName, ThisTypeName, nameof(GetDependencyInfoAsync), stopwatch.Elapsed)); } }
public override async Task <DownloadResourceResult> GetDownloadResourceResultAsync( PackageIdentity identity, PackageDownloadContext downloadContext, string globalPackagesFolder, ILogger logger, CancellationToken token) { if (identity == null) { throw new ArgumentNullException(nameof(identity)); } if (downloadContext == null) { throw new ArgumentNullException(nameof(downloadContext)); } if (logger == null) { throw new ArgumentNullException(nameof(logger)); } var stopwatch = Stopwatch.StartNew(); try { var uri = await GetDownloadUrl(identity, logger, token); if (uri != null) { return(await GetDownloadResultUtility.GetDownloadResultAsync( _client, identity, uri, downloadContext, globalPackagesFolder, logger, token)); } return(new DownloadResourceResult(DownloadResourceResultStatus.NotFound)); } finally { ProtocolDiagnostics.RaiseEvent(new ProtocolDiagnosticResourceEvent( _source, resourceType: nameof(DownloadResource), type: nameof(DownloadResourceV3), method: nameof(GetDownloadResourceResultAsync), duration: stopwatch.Elapsed)); } }
/// <summary> /// Asynchronously gets a package downloader for a package identity. /// </summary> /// <param name="packageIdentity">A package identity.</param> /// <param name="cacheContext">A source cache context.</param> /// <param name="logger">A logger.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <returns>A task that represents the asynchronous operation. /// The task result (<see cref="Task{TResult}.Result" />) returns an <see cref="IPackageDownloader" />.</returns> /// <exception cref="ArgumentNullException">Thrown if <paramref name="packageIdentity" /> <c>null</c>.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="cacheContext" /> <c>null</c>.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="logger" /> <c>null</c>.</exception> /// <exception cref="OperationCanceledException">Thrown if <paramref name="cancellationToken" /> /// is cancelled.</exception> public override async Task <IPackageDownloader> GetPackageDownloaderAsync( PackageIdentity packageIdentity, SourceCacheContext cacheContext, ILogger logger, CancellationToken cancellationToken) { if (packageIdentity == null) { throw new ArgumentNullException(nameof(packageIdentity)); } if (cacheContext == null) { throw new ArgumentNullException(nameof(cacheContext)); } if (logger == null) { throw new ArgumentNullException(nameof(logger)); } var stopwatch = Stopwatch.StartNew(); try { cancellationToken.ThrowIfCancellationRequested(); var packageInfo = await GetPackageInfoAsync( packageIdentity.Id, packageIdentity.Version, cacheContext, logger, cancellationToken); if (packageInfo == null) { return(null); } return(new RemotePackageArchiveDownloader(SourceRepository.PackageSource.Source, this, packageInfo.Identity, cacheContext, logger)); } finally { ProtocolDiagnostics.RaiseEvent(new ProtocolDiagnosticResourceEvent( SourceRepository.PackageSource.Source, ResourceTypeName, ThisTypeName, nameof(GetPackageDownloaderAsync), stopwatch.Elapsed)); } }
/// <summary> /// Asynchronously gets all package versions for a package ID. /// </summary> /// <param name="id">A package ID.</param> /// <param name="cacheContext">A source cache context.</param> /// <param name="logger">A logger.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <returns>A task that represents the asynchronous operation. /// The task result (<see cref="Task{TResult}.Result" />) returns an /// <see cref="IEnumerable{NuGetVersion}" />.</returns> /// <exception cref="ArgumentException">Thrown if <paramref name="id" /> /// is either <c>null</c> or an empty string.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="cacheContext" /> <c>null</c>.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="logger" /> <c>null</c>.</exception> /// <exception cref="OperationCanceledException">Thrown if <paramref name="cancellationToken" /> /// is cancelled.</exception> public override async Task <IEnumerable <NuGetVersion> > GetAllVersionsAsync( string id, SourceCacheContext cacheContext, ILogger logger, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(id)) { throw new ArgumentException(Strings.ArgumentCannotBeNullOrEmpty, nameof(id)); } if (cacheContext == null) { throw new ArgumentNullException(nameof(cacheContext)); } if (logger == null) { throw new ArgumentNullException(nameof(logger)); } var stopwatch = Stopwatch.StartNew(); try { cancellationToken.ThrowIfCancellationRequested(); AddOrUpdateLogger(_plugin, logger); await _utilities.DoOncePerPluginLifetimeAsync( MessageMethod.SetLogLevel.ToString(), () => SetLogLevelAsync(logger, cancellationToken), cancellationToken); var packageInfos = await EnsurePackagesAsync(id, cacheContext, logger, cancellationToken); return(packageInfos.Keys); } finally { ProtocolDiagnostics.RaiseEvent(new ProtocolDiagnosticResourceEvent( _packageSource.Source, ResourceTypeName, ThisTypeName, nameof(GetAllVersionsAsync), stopwatch.Elapsed)); } }
/// <summary> /// Asynchronously check if exact package (id/version) exists at this source. /// </summary> /// <param name="id">A package id.</param> /// <param name="version">A package version.</param> /// <param name="cacheContext">A source cache context.</param> /// <param name="logger">A logger.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <returns>A task that represents the asynchronous operation. /// The task result (<see cref="Task{TResult}.Result" />) returns an /// <see cref="IEnumerable{NuGetVersion}" />.</returns> /// <exception cref="ArgumentException">Thrown if <paramref name="id" /> /// is either <c>null</c> or an empty string.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="version" /> <c>null</c>.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="cacheContext" /> <c>null</c>.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="logger" /> <c>null</c>.</exception> /// <exception cref="OperationCanceledException">Thrown if <paramref name="cancellationToken" /> /// is cancelled.</exception> public override async Task <bool> DoesPackageExistAsync( string id, NuGetVersion version, SourceCacheContext cacheContext, ILogger logger, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(id)) { throw new ArgumentException(Strings.ArgumentCannotBeNullOrEmpty, nameof(id)); } if (version == null) { throw new ArgumentNullException(nameof(version)); } if (cacheContext == null) { throw new ArgumentNullException(nameof(cacheContext)); } if (logger == null) { throw new ArgumentNullException(nameof(logger)); } var stopwatch = Stopwatch.StartNew(); try { cancellationToken.ThrowIfCancellationRequested(); var packageInfo = await GetPackageInfoAsync(id, version, cacheContext, logger, cancellationToken); return(packageInfo != null); } finally { ProtocolDiagnostics.RaiseEvent(new ProtocolDiagnosticResourceEvent( SourceRepository.PackageSource.Source, ResourceTypeName, ThisTypeName, nameof(DoesPackageExistAsync), stopwatch.Elapsed)); } }
/// <summary> /// Asynchronously gets a package downloader for a package identity. /// </summary> /// <param name="packageIdentity">A package identity.</param> /// <param name="cacheContext">A source cache context.</param> /// <param name="logger">A logger.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <returns>A task that represents the asynchronous operation. /// The task result (<see cref="Task{TResult}.Result" />) returns an <see cref="IPackageDownloader" />.</returns> /// <exception cref="ArgumentNullException">Thrown if <paramref name="packageIdentity" /> <c>null</c>.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="cacheContext" /> <c>null</c>.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="logger" /> <c>null</c>.</exception> /// <exception cref="OperationCanceledException">Thrown if <paramref name="cancellationToken" /> /// is cancelled.</exception> public override Task <IPackageDownloader> GetPackageDownloaderAsync( PackageIdentity packageIdentity, SourceCacheContext cacheContext, ILogger logger, CancellationToken cancellationToken) { if (packageIdentity == null) { throw new ArgumentNullException(nameof(packageIdentity)); } if (cacheContext == null) { throw new ArgumentNullException(nameof(cacheContext)); } if (logger == null) { throw new ArgumentNullException(nameof(logger)); } var stopwatch = Stopwatch.StartNew(); try { cancellationToken.ThrowIfCancellationRequested(); var packageInfo = GetPackageInfo(packageIdentity.Id, packageIdentity.Version, cacheContext, logger); IPackageDownloader packageDownloader = null; if (packageInfo != null) { packageDownloader = new LocalPackageArchiveDownloader(_source, packageInfo.Path, packageInfo.Identity, logger); } return(Task.FromResult(packageDownloader)); } finally { ProtocolDiagnostics.RaiseEvent(new ProtocolDiagnosticResourceEvent( _source, ResourceTypeName, ThisTypeName, nameof(GetPackageDownloaderAsync), stopwatch.Elapsed)); } }
/// <summary> /// Copies a .nupkg stream to the <paramref name="destination"/> stream. If the .nupkg cannot be found or if /// there is a network problem, no stream copy occurs. /// </summary> /// <param name="identity">The package identity.</param> /// <param name="url">The URL of the .nupkg.</param> /// <param name="destination">The destination stream. The .nupkg will be copied to this stream.</param> /// <param name="cacheContext">The cache context.</param> /// <param name="token">The cancellation token.</param> /// <returns>Returns true if the stream was copied, false otherwise.</returns> public async Task <bool> CopyNupkgToStreamAsync( PackageIdentity identity, string url, Stream destination, SourceCacheContext cacheContext, ILogger logger, CancellationToken token) { if (!destination.CanSeek) { // In order to handle retries, we need to write to a temporary file, then copy to destination in one pass. string tempFilePath = Path.GetTempFileName(); using Stream tempFile = new FileStream(tempFilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None, bufferSize: 4096, FileOptions.DeleteOnClose); bool result = await CopyNupkgToStreamAsync(identity, url, tempFile, cacheContext, logger, token); tempFile.Position = 0; await tempFile.CopyToAsync(destination, token); return(result); } else { return(await ProcessNupkgStreamAsync( identity, url, async stream => { try { await stream.CopyToAsync(destination, token); ProtocolDiagnostics.RaiseEvent(new ProtocolDiagnosticNupkgCopiedEvent(_httpSource.PackageSource, destination.Length)); } catch when(!token.IsCancellationRequested) { destination.Position = 0; destination.SetLength(0); throw; } }, cacheContext, logger, token)); } }
/// <summary> /// Asynchronously gets a package downloader for a package identity. /// </summary> /// <param name="packageIdentity">A package identity.</param> /// <param name="cacheContext">A source cache context.</param> /// <param name="logger">A logger.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <returns>A task that represents the asynchronous operation. /// The task result (<see cref="Task{TResult}.Result" />) returns an <see cref="IPackageDownloader" />.</returns> /// <exception cref="ArgumentNullException">Thrown if <paramref name="packageIdentity" /> <c>null</c>.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="cacheContext" /> <c>null</c>.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="logger" /> <c>null</c>.</exception> /// <exception cref="OperationCanceledException">Thrown if <paramref name="cancellationToken" /> /// is cancelled.</exception> public override Task <IPackageDownloader> GetPackageDownloaderAsync( PackageIdentity packageIdentity, SourceCacheContext cacheContext, ILogger logger, CancellationToken cancellationToken) { if (packageIdentity == null) { throw new ArgumentNullException(nameof(packageIdentity)); } if (cacheContext == null) { throw new ArgumentNullException(nameof(cacheContext)); } if (logger == null) { throw new ArgumentNullException(nameof(logger)); } var stopwatch = Stopwatch.StartNew(); try { cancellationToken.ThrowIfCancellationRequested(); var packageReader = new PluginPackageReader(_plugin, packageIdentity, _packageSource.Source); var packageDependency = new PluginPackageDownloader(_plugin, packageIdentity, packageReader, _packageSource.Source); return(Task.FromResult <IPackageDownloader>(packageDependency)); } finally { ProtocolDiagnostics.RaiseEvent(new ProtocolDiagnosticResourceEvent( _packageSource.Source, ResourceTypeName, ThisTypeName, nameof(GetPackageDownloaderAsync), stopwatch.Elapsed)); } }
/// <summary> /// Copies a .nupkg stream to the <paramref name="destination"/> stream. If the .nupkg cannot be found or if /// there is a network problem, no stream copy occurs. /// </summary> /// <param name="identity">The package identity.</param> /// <param name="url">The URL of the .nupkg.</param> /// <param name="destination">The destination stream. The .nupkg will be copied to this stream.</param> /// <param name="cacheContext">The cache context.</param> /// <param name="token">The cancellation token.</param> /// <returns>Returns true if the stream was copied, false otherwise.</returns> public async Task <bool> CopyNupkgToStreamAsync( PackageIdentity identity, string url, Stream destination, SourceCacheContext cacheContext, ILogger logger, CancellationToken token) { return(await ProcessNupkgStreamAsync( identity, url, async stream => { await stream.CopyToAsync(destination, token); ProtocolDiagnostics.RaiseEvent(new ProtocolDiagnosticNupkgCopiedEvent(_httpSource.PackageSource, destination.Length)); }, cacheContext, logger, token)); }
/// <summary> /// Asynchronously gets all package versions for a package ID. /// </summary> /// <param name="id">A package ID.</param> /// <param name="cacheContext">A source cache context.</param> /// <param name="logger">A logger.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <returns>A task that represents the asynchronous operation. /// The task result (<see cref="Task{TResult}.Result" />) returns an /// <see cref="IEnumerable{NuGetVersion}" />.</returns> /// <exception cref="ArgumentException">Thrown if <paramref name="id" /> /// is either <c>null</c> or an empty string.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="cacheContext" /> <c>null</c>.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="logger" /> <c>null</c>.</exception> /// <exception cref="OperationCanceledException">Thrown if <paramref name="cancellationToken" /> /// is cancelled.</exception> public override Task <IEnumerable <NuGetVersion> > GetAllVersionsAsync( string id, SourceCacheContext cacheContext, ILogger logger, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(id)) { throw new ArgumentException(Strings.ArgumentCannotBeNullOrEmpty, nameof(id)); } if (cacheContext == null) { throw new ArgumentNullException(nameof(cacheContext)); } if (logger == null) { throw new ArgumentNullException(nameof(logger)); } var stopwatch = Stopwatch.StartNew(); try { cancellationToken.ThrowIfCancellationRequested(); var infos = GetPackageInfos(id, cacheContext, logger); return(Task.FromResult(infos.Select(p => p.Identity.Version))); } finally { ProtocolDiagnostics.RaiseEvent(new ProtocolDiagnosticResourceEvent( _source, ResourceTypeName, ThisTypeName, nameof(GetAllVersionsAsync), stopwatch.Elapsed)); } }
/// <summary> /// Asynchronously gets all package versions for a package ID. /// </summary> /// <param name="id">A package ID.</param> /// <param name="cacheContext">A source cache context.</param> /// <param name="logger">A logger.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <returns>A task that represents the asynchronous operation. /// The task result (<see cref="Task{TResult}.Result" />) returns an /// <see cref="IEnumerable{NuGetVersion}" />.</returns> /// <exception cref="ArgumentException">Thrown if <paramref name="id" /> /// is either <c>null</c> or an empty string.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="cacheContext" /> <c>null</c>.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="logger" /> <c>null</c>.</exception> /// <exception cref="OperationCanceledException">Thrown if <paramref name="cancellationToken" /> /// is cancelled.</exception> public override async Task <IEnumerable <NuGetVersion> > GetAllVersionsAsync( string id, SourceCacheContext cacheContext, ILogger logger, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(id)) { throw new ArgumentException(Strings.ArgumentCannotBeNullOrEmpty, nameof(id)); } if (cacheContext == null) { throw new ArgumentNullException(nameof(cacheContext)); } if (logger == null) { throw new ArgumentNullException(nameof(logger)); } var stopwatch = Stopwatch.StartNew(); try { cancellationToken.ThrowIfCancellationRequested(); var result = await EnsurePackagesAsync(id, cacheContext, logger, cancellationToken); return(result.Select(item => item.Identity.Version)); } finally { ProtocolDiagnostics.RaiseEvent(new ProtocolDiagnosticResourceEvent( SourceRepository.PackageSource.Source, ResourceTypeName, ThisTypeName, nameof(GetAllVersionsAsync), stopwatch.Elapsed)); } }
/// <summary> /// Asynchronously gets dependency information for a specific package. /// </summary> /// <param name="id">A package id.</param> /// <param name="version">A package version.</param> /// <param name="cacheContext">A source cache context.</param> /// <param name="logger">A logger.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <returns>A task that represents the asynchronous operation. /// The task result (<see cref="Task{TResult}.Result" />) returns an /// <see cref="IEnumerable{NuGetVersion}" />.</returns> /// <exception cref="ArgumentException">Thrown if <paramref name="id" /> /// is either <c>null</c> or an empty string.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="version" /> <c>null</c>.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="cacheContext" /> <c>null</c>.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="logger" /> <c>null</c>.</exception> /// <exception cref="OperationCanceledException">Thrown if <paramref name="cancellationToken" /> /// is cancelled.</exception> public override async Task <FindPackageByIdDependencyInfo> GetDependencyInfoAsync( string id, NuGetVersion version, SourceCacheContext cacheContext, ILogger logger, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(id)) { throw new ArgumentException(Strings.ArgumentCannotBeNullOrEmpty, nameof(id)); } if (version == null) { throw new ArgumentNullException(nameof(version)); } if (cacheContext == null) { throw new ArgumentNullException(nameof(cacheContext)); } if (logger == null) { throw new ArgumentNullException(nameof(logger)); } var stopwatch = Stopwatch.StartNew(); try { cancellationToken.ThrowIfCancellationRequested(); var packageInfos = await EnsurePackagesAsync(id, cacheContext, logger, cancellationToken); PackageInfo packageInfo; if (packageInfos.TryGetValue(version, out packageInfo)) { AddOrUpdateLogger(_plugin, logger); await _utilities.DoOncePerPluginLifetimeAsync( MessageMethod.SetLogLevel.ToString(), () => SetLogLevelAsync(logger, cancellationToken), cancellationToken); var response = await _plugin.Connection.SendRequestAndReceiveResponseAsync <PrefetchPackageRequest, PrefetchPackageResponse>( MessageMethod.PrefetchPackage, new PrefetchPackageRequest( _packageSource.Source, packageInfo.Identity.Id, packageInfo.Identity.Version.ToNormalizedString()), cancellationToken); if (response != null && response.ResponseCode == MessageResponseCode.Success) { using (var packageReader = new PluginPackageReader(_plugin, packageInfo.Identity, _packageSource.Source)) { var nuspecReader = await packageReader.GetNuspecReaderAsync(cancellationToken); return(GetDependencyInfo(nuspecReader)); } } } return(null); } finally { ProtocolDiagnostics.RaiseEvent(new ProtocolDiagnosticResourceEvent( _packageSource.Source, ResourceTypeName, ThisTypeName, nameof(GetDependencyInfoAsync), stopwatch.Elapsed)); } }
public override async Task <DownloadResourceResult> GetDownloadResourceResultAsync( PackageIdentity identity, PackageDownloadContext downloadContext, string globalPackagesFolder, ILogger logger, CancellationToken token) { if (identity == null) { throw new ArgumentNullException(nameof(identity)); } if (downloadContext == null) { throw new ArgumentNullException(nameof(downloadContext)); } if (logger == null) { throw new ArgumentNullException(nameof(logger)); } var stopwatch = Stopwatch.StartNew(); try { token.ThrowIfCancellationRequested(); var sourcePackage = identity as SourcePackageDependencyInfo; bool isFromUri = sourcePackage?.PackageHash != null && sourcePackage?.DownloadUri != null; try { if (isFromUri) { // If this is a SourcePackageDependencyInfo object with everything populated // and it is from an online source, use the machine cache and download it using the // given url. return(await _feedParser.DownloadFromUrl( sourcePackage, sourcePackage.DownloadUri, downloadContext, globalPackagesFolder, logger, token)); } else { using (var sourceCacheContext = new SourceCacheContext()) { // Look up the package from the id and version and download it. return(await _feedParser.DownloadFromIdentity( identity, downloadContext, globalPackagesFolder, sourceCacheContext, logger, token)); } } } catch (OperationCanceledException) { return(new DownloadResourceResult(DownloadResourceResultStatus.Cancelled)); } catch (Exception ex) when(!(ex is FatalProtocolException)) { string message = string.Format(CultureInfo.CurrentCulture, Strings.Log_ErrorDownloading, identity, _feedParser.Source); throw new FatalProtocolException(message, ex); } } finally { ProtocolDiagnostics.RaiseEvent(new ProtocolDiagnosticResourceEvent( _source, resourceType: nameof(DownloadResource), type: nameof(DownloadResourceV2Feed), method: nameof(GetDownloadResourceResultAsync), stopwatch.Elapsed)); } }
/// <summary> /// Asynchronously copies a .nupkg to a stream. /// </summary> /// <param name="id">A package ID.</param> /// <param name="version">A package version.</param> /// <param name="destination">A destination stream.</param> /// <param name="cacheContext">A source cache context.</param> /// <param name="logger">A logger.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <returns>A task that represents the asynchronous operation. /// The task result (<see cref="Task{TResult}.Result" />) returns an /// <see cref="bool" /> indicating whether or not the .nupkg file was copied.</returns> /// <exception cref="ArgumentException">Thrown if <paramref name="id" /> /// is either <c>null</c> or an empty string.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="version" /> <c>null</c>.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="destination" /> <c>null</c>.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="cacheContext" /> <c>null</c>.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="logger" /> <c>null</c>.</exception> /// <exception cref="OperationCanceledException">Thrown if <paramref name="cancellationToken" /> /// is cancelled.</exception> public override async Task <bool> CopyNupkgToStreamAsync( string id, NuGetVersion version, Stream destination, SourceCacheContext cacheContext, ILogger logger, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(id)) { throw new ArgumentException(Strings.ArgumentCannotBeNullOrEmpty, nameof(id)); } if (version == null) { throw new ArgumentNullException(nameof(version)); } if (destination == null) { throw new ArgumentNullException(nameof(destination)); } if (cacheContext == null) { throw new ArgumentNullException(nameof(cacheContext)); } if (logger == null) { throw new ArgumentNullException(nameof(logger)); } var stopwatch = Stopwatch.StartNew(); try { cancellationToken.ThrowIfCancellationRequested(); var packageInfo = await GetPackageInfoAsync(id, version, cacheContext, logger, cancellationToken); if (packageInfo == null) { return(false); } return(await _nupkgDownloader.CopyNupkgToStreamAsync( packageInfo.Identity, packageInfo.ContentUri, destination, cacheContext, logger, cancellationToken)); } finally { ProtocolDiagnostics.RaiseEvent(new ProtocolDiagnosticResourceEvent( SourceRepository.PackageSource.Source, ResourceTypeName, ThisTypeName, nameof(CopyNupkgToStreamAsync), stopwatch.Elapsed)); } }
/// <summary> /// Make an HTTP request while retrying after failed attempts or timeouts. /// </summary> /// <remarks> /// This method accepts a factory to create instances of the <see cref="HttpRequestMessage"/> because /// requests cannot always be used. For example, suppose the request is a POST and contains content /// of a stream that can only be consumed once. /// </remarks> public async Task <HttpResponseMessage> SendAsync( HttpRetryHandlerRequest request, string source, ILogger log, CancellationToken cancellationToken) { if (source == null) { throw new ArgumentNullException(nameof(source)); } // If specified via environment, override the default retry delay with the values provided if (_enhancedHttpRetryHelper.IsEnabled) { request.RetryDelay = TimeSpan.FromMilliseconds(_enhancedHttpRetryHelper.DelayInMilliseconds); } var tries = 0; HttpResponseMessage response = null; var success = false; while (tries < request.MaxTries && !success) { // There are many places where another variable named "MaxTries" is set to 1, // so the Delay() never actually occurs. // When opted in to "enhanced retry", do the delay and have it increase exponentially where applicable // (i.e. when "tries" is allowed to be > 1) if (tries > 0 || (_enhancedHttpRetryHelper.IsEnabled && request.IsRetry)) { // "Enhanced" retry: In the case where this is actually a 2nd-Nth try, back off exponentially with some random. // In many cases due to the external retry loop, this will be always be 1 * request.RetryDelay.TotalMilliseconds + 0-200 ms if (_enhancedHttpRetryHelper.IsEnabled) { if (tries >= 3 || (tries == 0 && request.IsRetry)) { log.LogVerbose("Enhanced retry: HttpRetryHandler is in a state that retry would have been abandoned or not waited if it were not enabled."); } await Task.Delay(TimeSpan.FromMilliseconds((Math.Pow(2, tries) * request.RetryDelay.TotalMilliseconds) + new Random().Next(200)), cancellationToken); } // Old behavior; always delay a constant amount else { await Task.Delay(request.RetryDelay, cancellationToken); } } tries++; success = true; using (var requestMessage = request.RequestFactory()) { var stopwatches = new List <Stopwatch>(2); var bodyStopwatch = new Stopwatch(); stopwatches.Add(bodyStopwatch); Stopwatch headerStopwatch = null; if (request.CompletionOption == HttpCompletionOption.ResponseHeadersRead) { headerStopwatch = new Stopwatch(); stopwatches.Add(headerStopwatch); } #if NET5_0 requestMessage.Options.Set(new HttpRequestOptionsKey <List <Stopwatch> >(StopwatchPropertyName), stopwatches); #else requestMessage.Properties[StopwatchPropertyName] = stopwatches; #endif var requestUri = requestMessage.RequestUri; try { // The only time that we will be disposing this existing response is if we have // successfully fetched an HTTP response but the response has an status code indicating // failure (i.e. HTTP status code >= 500). // // If we don't even get an HTTP response message because an exception is thrown, then there // is no response instance to dispose. Additionally, we cannot use a finally here because // the caller needs the response instance returned in a non-disposed state. // // Also, remember that if an HTTP server continuously returns a failure status code (like // 500 Internal Server Error), we will retry some number of times but eventually return the // response as-is, expecting the caller to check the status code as well. This results in the // success variable being set to false but the response being returned to the caller without // disposing it. response?.Dispose(); // Add common headers to the request after it is created by the factory. This includes // X-NuGet-Session-Id which is added to all nuget requests. foreach (var header in request.AddHeaders) { requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value); } log.LogInformation(" " + string.Format( CultureInfo.InvariantCulture, Strings.Http_RequestLog, requestMessage.Method, requestUri)); // Issue the request. var timeoutMessage = string.Format( CultureInfo.CurrentCulture, Strings.Http_Timeout, requestMessage.Method, requestUri, (int)request.RequestTimeout.TotalMilliseconds); response = await TimeoutUtility.StartWithTimeout( async timeoutToken => { bodyStopwatch.Start(); headerStopwatch?.Start(); var responseMessage = await request.HttpClient.SendAsync(requestMessage, request.CompletionOption, timeoutToken); headerStopwatch?.Stop(); return(responseMessage); }, request.RequestTimeout, timeoutMessage, cancellationToken); // Wrap the response stream so that the download can timeout. if (response.Content != null) { var networkStream = await response.Content.ReadAsStreamAsync(); var timeoutStream = new DownloadTimeoutStream(requestUri.ToString(), networkStream, request.DownloadTimeout); var inProgressEvent = new ProtocolDiagnosticInProgressHttpEvent( source, requestUri, headerStopwatch?.Elapsed, (int)response.StatusCode, isRetry: request.IsRetry || tries > 1, isCancelled: false, isLastAttempt: tries == request.MaxTries && request.IsLastAttempt); var diagnosticsStream = new ProtocolDiagnosticsStream(timeoutStream, inProgressEvent, bodyStopwatch, ProtocolDiagnostics.RaiseEvent); var newContent = new StreamContent(diagnosticsStream); // Copy over the content headers since we are replacing the HttpContent instance associated // with the response message. foreach (var header in response.Content.Headers) { newContent.Headers.TryAddWithoutValidation(header.Key, header.Value); } response.Content = newContent; } log.LogInformation(" " + string.Format( CultureInfo.InvariantCulture, Strings.Http_ResponseLog, response.StatusCode, requestUri, bodyStopwatch.ElapsedMilliseconds)); if ((int)response.StatusCode >= 500) { success = false; } } catch (OperationCanceledException) { response?.Dispose(); ProtocolDiagnostics.RaiseEvent(new ProtocolDiagnosticHttpEvent( timestamp: DateTime.UtcNow, source, requestUri, headerDuration: null, eventDuration: bodyStopwatch.Elapsed, httpStatusCode: null, bytes: 0, isSuccess: false, isRetry: request.IsRetry || tries > 1, isCancelled: true, isLastAttempt: tries == request.MaxTries && request.IsLastAttempt)); throw; } catch (Exception e) { success = false; response?.Dispose(); ProtocolDiagnostics.RaiseEvent(new ProtocolDiagnosticHttpEvent( timestamp: DateTime.UtcNow, source, requestUri, headerDuration: null, eventDuration: bodyStopwatch.Elapsed, httpStatusCode: null, bytes: 0, isSuccess: false, isRetry: request.IsRetry || tries > 1, isCancelled: false, isLastAttempt: tries == request.MaxTries && request.IsLastAttempt)); if (tries >= request.MaxTries) { throw; } log.LogInformation(string.Format( CultureInfo.CurrentCulture, Strings.Log_RetryingHttp, requestMessage.Method, requestUri, requestMessage) + Environment.NewLine + ExceptionUtilities.DisplayMessage(e)); } } } return(response); }
/// <summary> /// Make an HTTP request while retrying after failed attempts or timeouts. /// </summary> /// <remarks> /// This method accepts a factory to create instances of the <see cref="HttpRequestMessage"/> because /// requests cannot always be used. For example, suppose the request is a POST and contains content /// of a stream that can only be consumed once. /// </remarks> public async Task <HttpResponseMessage> SendAsync( HttpRetryHandlerRequest request, string source, ILogger log, CancellationToken cancellationToken) { if (source == null) { throw new ArgumentNullException(nameof(source)); } var tries = 0; HttpResponseMessage response = null; var success = false; while (tries < request.MaxTries && !success) { if (tries > 0) { await Task.Delay(request.RetryDelay, cancellationToken); } tries++; success = true; using (var requestMessage = request.RequestFactory()) { var stopwatches = new List <Stopwatch>(2); var bodyStopwatch = new Stopwatch(); stopwatches.Add(bodyStopwatch); Stopwatch headerStopwatch = null; if (request.CompletionOption == HttpCompletionOption.ResponseHeadersRead) { headerStopwatch = new Stopwatch(); stopwatches.Add(headerStopwatch); } requestMessage.Properties[StopwatchPropertyName] = stopwatches; var requestUri = requestMessage.RequestUri; try { // The only time that we will be disposing this existing response is if we have // successfully fetched an HTTP response but the response has an status code indicating // failure (i.e. HTTP status code >= 500). // // If we don't even get an HTTP response message because an exception is thrown, then there // is no response instance to dispose. Additionally, we cannot use a finally here because // the caller needs the response instance returned in a non-disposed state. // // Also, remember that if an HTTP server continuously returns a failure status code (like // 500 Internal Server Error), we will retry some number of times but eventually return the // response as-is, expecting the caller to check the status code as well. This results in the // success variable being set to false but the response being returned to the caller without // disposing it. response?.Dispose(); // Add common headers to the request after it is created by the factory. This includes // X-NuGet-Session-Id which is added to all nuget requests. foreach (var header in request.AddHeaders) { requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value); } log.LogInformation(" " + string.Format( CultureInfo.InvariantCulture, Strings.Http_RequestLog, requestMessage.Method, requestUri)); // Issue the request. var timeoutMessage = string.Format( CultureInfo.CurrentCulture, Strings.Http_Timeout, requestMessage.Method, requestUri, (int)request.RequestTimeout.TotalMilliseconds); response = await TimeoutUtility.StartWithTimeout( async timeoutToken => { bodyStopwatch.Start(); headerStopwatch?.Start(); var responseMessage = await request.HttpClient.SendAsync(requestMessage, request.CompletionOption, timeoutToken); headerStopwatch?.Stop(); return(responseMessage); }, request.RequestTimeout, timeoutMessage, cancellationToken); // Wrap the response stream so that the download can timeout. if (response.Content != null) { var networkStream = await response.Content.ReadAsStreamAsync(); var timeoutStream = new DownloadTimeoutStream(requestUri.ToString(), networkStream, request.DownloadTimeout); var inProgressEvent = new ProtocolDiagnosticInProgressHttpEvent( source, requestUri, headerStopwatch?.Elapsed, (int)response.StatusCode, isRetry: request.IsRetry || tries > 1, isCancelled: false, isLastAttempt: tries == request.MaxTries && request.IsLastAttempt); var diagnosticsStream = new ProtocolDiagnosticsStream(timeoutStream, inProgressEvent, bodyStopwatch, ProtocolDiagnostics.RaiseEvent); var newContent = new StreamContent(diagnosticsStream); // Copy over the content headers since we are replacing the HttpContent instance associated // with the response message. foreach (var header in response.Content.Headers) { newContent.Headers.TryAddWithoutValidation(header.Key, header.Value); } response.Content = newContent; } log.LogInformation(" " + string.Format( CultureInfo.InvariantCulture, Strings.Http_ResponseLog, response.StatusCode, requestUri, bodyStopwatch.ElapsedMilliseconds)); if ((int)response.StatusCode >= 500) { success = false; } } catch (OperationCanceledException) { response?.Dispose(); ProtocolDiagnostics.RaiseEvent(new ProtocolDiagnosticHttpEvent( timestamp: DateTime.UtcNow, source, requestUri, headerDuration: null, eventDuration: bodyStopwatch.Elapsed, httpStatusCode: null, bytes: 0, isSuccess: false, isRetry: request.IsRetry || tries > 1, isCancelled: true, isLastAttempt: tries == request.MaxTries && request.IsLastAttempt)); throw; } catch (Exception e) { success = false; response?.Dispose(); ProtocolDiagnostics.RaiseEvent(new ProtocolDiagnosticHttpEvent( timestamp: DateTime.UtcNow, source, requestUri, headerDuration: null, eventDuration: bodyStopwatch.Elapsed, httpStatusCode: null, bytes: 0, isSuccess: false, isRetry: request.IsRetry || tries > 1, isCancelled: false, isLastAttempt: tries == request.MaxTries && request.IsLastAttempt)); if (tries >= request.MaxTries) { throw; } log.LogInformation(string.Format( CultureInfo.CurrentCulture, Strings.Log_RetryingHttp, requestMessage.Method, requestUri, requestMessage) + Environment.NewLine + ExceptionUtilities.DisplayMessage(e)); } } } return(response); }
/// <summary> /// Asynchronously copies a .nupkg to a stream. /// </summary> /// <param name="id">A package ID.</param> /// <param name="version">A package version.</param> /// <param name="destination">A destination stream.</param> /// <param name="cacheContext">A source cache context.</param> /// <param name="logger">A logger.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <returns>A task that represents the asynchronous operation. /// The task result (<see cref="Task{TResult}.Result" />) returns an /// <see cref="bool" /> indicating whether or not the .nupkg file was copied.</returns> /// <exception cref="ArgumentException">Thrown if <paramref name="id" /> /// is either <c>null</c> or an empty string.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="version" /> <c>null</c>.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="destination" /> <c>null</c>.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="cacheContext" /> <c>null</c>.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="logger" /> <c>null</c>.</exception> /// <exception cref="OperationCanceledException">Thrown if <paramref name="cancellationToken" /> /// is cancelled.</exception> public override async Task <bool> CopyNupkgToStreamAsync( string id, NuGetVersion version, Stream destination, SourceCacheContext cacheContext, ILogger logger, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(id)) { throw new ArgumentException(Strings.ArgumentCannotBeNullOrEmpty, nameof(id)); } if (version == null) { throw new ArgumentNullException(nameof(version)); } if (destination == null) { throw new ArgumentNullException(nameof(destination)); } if (cacheContext == null) { throw new ArgumentNullException(nameof(cacheContext)); } if (logger == null) { throw new ArgumentNullException(nameof(logger)); } var stopwatch = Stopwatch.StartNew(); try { cancellationToken.ThrowIfCancellationRequested(); var info = GetPackageInfo(id, version, cacheContext, logger); if (info != null) { using (var fileStream = File.OpenRead(info.Path)) { await fileStream.CopyToAsync(destination, cancellationToken); ProtocolDiagnostics.RaiseEvent(new ProtocolDiagnosticNupkgCopiedEvent(_source, destination.Length)); return(true); } } return(false); } finally { ProtocolDiagnostics.RaiseEvent(new ProtocolDiagnosticResourceEvent( _source, ResourceTypeName, ThisTypeName, nameof(CopyNupkgToStreamAsync), stopwatch.Elapsed)); } }
/// <summary> /// Asynchronously downloads a package. /// </summary> /// <param name="identity">The package identity.</param> /// <param name="downloadContext">A package download context.</param> /// <param name="globalPackagesFolder">The path to the global packages folder.</param> /// <param name="logger">A logger.</param> /// <param name="cancellationToken">A cancellation token.</param> /// <returns>A task that represents the asynchronous operation. /// The task result (<see cref="Task{TResult}.Result" />) returns /// a <see cref="DownloadResourceResult" />.</returns> /// <exception cref="ArgumentNullException">Thrown if <paramref name="identity" /> is <c>null</c>.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="downloadContext" /> /// is <c>null</c>.</exception> /// <exception cref="ArgumentNullException">Thrown if <paramref name="logger" /> is <c>null</c>.</exception> /// <exception cref="OperationCanceledException">Thrown if <paramref name="cancellationToken" /> /// is cancelled.</exception> public async override Task <DownloadResourceResult> GetDownloadResourceResultAsync( PackageIdentity identity, PackageDownloadContext downloadContext, string globalPackagesFolder, ILogger logger, CancellationToken cancellationToken) { if (identity == null) { throw new ArgumentNullException(nameof(identity)); } if (downloadContext == null) { throw new ArgumentNullException(nameof(downloadContext)); } if (logger == null) { throw new ArgumentNullException(nameof(logger)); } var stopwatch = Stopwatch.StartNew(); try { cancellationToken.ThrowIfCancellationRequested(); AddOrUpdateLogger(_plugin, logger); await _utilities.DoOncePerPluginLifetimeAsync( MessageMethod.SetLogLevel.ToString(), () => SetLogLevelAsync(logger, cancellationToken), cancellationToken); var response = await _plugin.Connection.SendRequestAndReceiveResponseAsync <PrefetchPackageRequest, PrefetchPackageResponse>( MessageMethod.PrefetchPackage, new PrefetchPackageRequest(_packageSource.Source, identity.Id, identity.Version.ToNormalizedString()), cancellationToken); if (response != null) { if (response.ResponseCode == MessageResponseCode.Success) { var packageReader = new PluginPackageReader(_plugin, identity, _packageSource.Source); return(new DownloadResourceResult(packageReader, _packageSource.Source)); } if (response.ResponseCode == MessageResponseCode.NotFound) { return(new DownloadResourceResult(DownloadResourceResultStatus.NotFound)); } } throw new PluginException( string.Format(CultureInfo.CurrentCulture, Strings.Plugin_PackageDownloadFailed, _plugin.Name, $"{identity.Id}.{identity.Version.ToNormalizedString()}")); } finally { ProtocolDiagnostics.RaiseEvent(new ProtocolDiagnosticResourceEvent( _packageSource.Source, resourceType: nameof(DownloadResource), type: nameof(DownloadResourcePlugin), method: nameof(GetDownloadResourceResultAsync), duration: stopwatch.Elapsed)); } }