File: PackagePreFetcherResult.cs
Web Access
Project: src\src\nuget-client\src\NuGet.Core\NuGet.PackageManagement\NuGet.PackageManagement.csproj (NuGet.PackageManagement)
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

#nullable disable

using System;
using System.IO;
using System.Runtime.ExceptionServices;
using System.Threading.Tasks;
using NuGet.Common;
using NuGet.Packaging;
using NuGet.Packaging.Core;
using NuGet.Protocol.Core.Types;

namespace NuGet.PackageManagement
{
    public class PackagePreFetcherResult : IDisposable
    {
        private readonly Task<DownloadResourceResult> _downloadTask;
        private readonly string _nupkgPath;
        private DownloadResourceResult _result;
        private ExceptionDispatchInfo _exception;
        private readonly DateTimeOffset _downloadStartTime;
        private DateTimeOffset _packageFetchTime;
        private DateTimeOffset _taskReturnTime;
        private bool _disposed = false;

        /// <summary>
        /// True if the result came from the packages folder.
        /// </summary>
        /// <remarks>Not thread safe.</remarks>
        public bool InPackagesFolder { get; }

        /// <summary>
        /// Package identity.
        /// </summary>
        public PackageIdentity Package { get; }

        /// <summary>
        /// PackageSource for the download. This is null if the packages folder was used.
        /// </summary>
        public Configuration.PackageSource Source { get; }

        /// <summary>
        /// True if the download is complete.
        /// </summary>
        public bool IsComplete { get; private set; }

        public const string PackagePreFetcherInformation = nameof(PackagePreFetcherInformation);

        /// <summary>
        /// Create a PreFetcher result for a downloaded package.
        /// </summary>
        public PackagePreFetcherResult(
            Task<DownloadResourceResult> downloadTask,
            PackageIdentity package,
            Configuration.PackageSource source)
        {
            if (source == null)
            {
                throw new ArgumentNullException(nameof(source));
            }

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

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

            _downloadTask = downloadTask;
            Package = package;
            InPackagesFolder = false;
            Source = source;
            _downloadStartTime = DateTimeOffset.Now;
        }

        /// <summary>
        /// Create a PreFetcher result for a package in the packages folder.
        /// </summary>
        public PackagePreFetcherResult(
            string nupkgPath,
            PackageIdentity package)
        {
            if (nupkgPath == null)
            {
                throw new ArgumentNullException(nameof(nupkgPath));
            }

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

            InPackagesFolder = true;
            IsComplete = true;
            Package = package;
            _nupkgPath = nupkgPath;
        }

        /// <summary>
        /// A safe wait for the download task. Exceptions are caught and stored.
        /// </summary>
        public async Task EnsureResultAsync()
        {
            // This is a noop if this has been called before, or if the result is in the packages folder.
            if (!InPackagesFolder && _result == null)
            {
                _packageFetchTime = DateTimeOffset.Now;
                try
                {
                    _result = await _downloadTask;
                }
                catch (Exception ex)
                {
                    _exception = ExceptionDispatchInfo.Capture(ex);
                }

                _taskReturnTime = DateTimeOffset.Now;

                IsComplete = true;
            }
        }

        /// <summary>
        /// Ensure and retrieve the download result.
        /// </summary>
        public async Task<DownloadResourceResult> GetResultAsync()
        {
            DownloadResourceResult result = null;

            if (InPackagesFolder)
            {
                // Results from the packages folder are created on demand
                result = GetPackagesFolderResult(_nupkgPath);
            }
            else
            {
                // Wait for the download to finish
                await EnsureResultAsync();

                if (_exception != null)
                {
                    // Rethrow the exception if the download failed
                    _exception.Throw();
                }

                // Use the downloadTask result
                result = _result;
            }

            return result;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (_disposed)
            {
                return;
            }

            if (disposing)
            {
                // The task should be awaited before calling dispose
                if (_result != null)
                {
                    _result.Dispose();
                }
            }

            _disposed = true;
        }

        public void EmitTelemetryEvent(Guid parentId)
        {
            var telemetryEvent = new TelemetryEvent(PackagePreFetcherInformation);

            telemetryEvent["DownloadStartTime"] = _downloadStartTime;
            telemetryEvent["PackageFetchTime"] = _packageFetchTime;
            telemetryEvent["TaskReturnTime"] = _taskReturnTime;

            telemetryEvent.AddPiiData("PackageId", Package.ToString());

            if (parentId != Guid.Empty)
            {
                telemetryEvent["ParentId"] = parentId.ToString();
            }

            TelemetryActivity.EmitTelemetryEvent(telemetryEvent);
        }

        private DownloadResourceResult GetPackagesFolderResult(string nupkgPath)
        {
            // Create a download result for the package that already exists
            return new DownloadResourceResult(
                File.OpenRead(nupkgPath),
                new PackageArchiveReader(nupkgPath),
                Source?.Source)
            { SignatureVerified = true };
        }
    }
}