private static async Task <NuGetDownloadResult> DownloadAsync(
            ILogger logger,
            Uri nugetExeUri,
            FileInfo targetFile,
            string targetFileTempPath,
            HttpClient?httpClient,
            CancellationToken cancellationToken)
        {
            logger.Debug("Downloading {Uri} to {TempFile}", nugetExeUri, targetFileTempPath);

            using (var request = new HttpRequestMessage())
            {
                request.RequestUri = nugetExeUri;

                bool owsClient = httpClient is null;

                httpClient ??= new HttpClient();

                try
                {
                    using HttpResponseMessage httpResponseMessage =
                              await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);

                    await using (var nugetExeFileStream =
                                     new FileStream(targetFileTempPath, FileMode.OpenOrCreate, FileAccess.Write))
                    {
                        if (!httpResponseMessage.IsSuccessStatusCode)
                        {
                            return(NuGetDownloadResult.DownloadFailed(httpResponseMessage.StatusCode));
                        }

                        await using Stream downloadStream =
                                        await httpResponseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false);

                        const int defaultBufferSize = 8192;
                        await downloadStream.CopyToAsync(nugetExeFileStream,
                                                         defaultBufferSize,
                                                         cancellationToken)
                        .ConfigureAwait(false);

                        await nugetExeFileStream.FlushAsync(cancellationToken).ConfigureAwait(false);

                        logger.Debug("Successfully downloaded {TempFile}", targetFileTempPath);
                    }

                    var fileInfo = new FileInfo(targetFileTempPath);

                    if (fileInfo.Length < 1024 * 1024)
                    {
                        return(NuGetDownloadResult.DownloadFailed("Downloaded file is not complete"));
                    }
                }
                finally
                {
                    if (owsClient)
                    {
                        httpClient.Dispose();
                    }
                }
            }

            if (File.Exists(targetFile.FullName))
            {
                await Task.Delay(TimeSpan.FromSeconds(2), cancellationToken).ConfigureAwait(false);
            }

            logger.Debug("Copying temp file {TempFile} to target file {TargetFile}",
                         targetFileTempPath,
                         targetFile.FullName);

            File.Copy(targetFileTempPath, targetFile.FullName, true);

            if (File.Exists(targetFileTempPath))
            {
                File.Delete(targetFileTempPath);

                logger.Debug("Deleted temp file {TempFile}", targetFileTempPath);
            }

            logger.Debug("NuGet client now exists at {TargetFile}", targetFile.FullName);

            return(NuGetDownloadResult.Success(targetFile.FullName));
        }
        public async Task <NuGetDownloadResult> DownloadNuGetInternalAsync(
            NuGetDownloadSettings nuGetDownloadSettings,
            [NotNull] ILogger logger,
            HttpClient?httpClient = null,
            CancellationToken cancellationToken = default)
        {
            if (!nuGetDownloadSettings.NugetDownloadEnabled)
            {
                return(NuGetDownloadResult.Disabled);
            }

            if (nuGetDownloadSettings.NugetDownloadUriFormat is null)
            {
                return(NuGetDownloadResult.MissingNuGetDownloadUriFormat);
            }

            if (string.IsNullOrWhiteSpace(nuGetDownloadSettings.NugetExeVersion))
            {
                return(NuGetDownloadResult.MissingNuGetExeVersion);
            }

            bool ownsClient = httpClient is null;

            httpClient ??= new HttpClient();

            try
            {
                string downloadDirectoryPath = nuGetDownloadSettings.DownloadDirectory.WithDefault(
                    Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
                                 "Arbor.Tooler",
                                 "tools",
                                 "nuget",
                                 nuGetDownloadSettings.NugetExeVersion)) !;

                var downloadDirectory = new DirectoryInfo(downloadDirectoryPath);

                try
                {
                    if (!downloadDirectory.Exists)
                    {
                        downloadDirectory.Create();
                    }
                }
                catch (Exception ex)
                {
                    return(NuGetDownloadResult.FromException(ex));
                }

                var targetFile = new FileInfo(Path.Combine(downloadDirectory.FullName, "nuget.exe"));

                string targetFileTempPath = Path.Combine(downloadDirectory.FullName,
                                                         $"nuget.exe-{DateTime.UtcNow.Ticks}.tmp");

                if (targetFile.Exists && !nuGetDownloadSettings.Force)
                {
                    logger.Debug("Found existing nuget.exe at {FilePath}, skipping download", targetFile);

                    if (nuGetDownloadSettings.UpdateEnabled)
                    {
                        NuGetDownloadResult?nuGetDownloadResult = await EnsureLatestAsync(targetFile,
                                                                                          targetFileTempPath,
                                                                                          logger,
                                                                                          httpClient,
                                                                                          cancellationToken).ConfigureAwait(false);

                        if (nuGetDownloadResult?.Succeeded == true)
                        {
                            return(nuGetDownloadResult);
                        }
                    }

                    return(NuGetDownloadResult.Success(targetFile.FullName));
                }

                string downloadUriFormat =
                    nuGetDownloadSettings.NugetDownloadUriFormat.WithDefault(NuGetDownloadSettings
                                                                             .DefaultNuGetExeDownloadUriFormat) !;

                string downloadUri = downloadUriFormat.Contains("{0}", StringComparison.OrdinalIgnoreCase)
                    ? string.Format(downloadUriFormat, nuGetDownloadSettings.NugetExeVersion)
                    : downloadUriFormat;

                if (!Uri.TryCreate(downloadUri, UriKind.Absolute, out Uri? nugetExeUri) ||
                    !nugetExeUri.IsHttpOrHttps())
                {
                    return(NuGetDownloadResult.InvalidDownloadUri(downloadUri));
                }

                NuGetDownloadResult result = await DownloadAsync(logger,
                                                                 nugetExeUri,
                                                                 targetFile,
                                                                 targetFileTempPath,
                                                                 httpClient,
                                                                 cancellationToken).ConfigureAwait(false);

                if (result.Succeeded && nuGetDownloadSettings.UpdateEnabled)
                {
                    NuGetDownloadResult?nuGetDownloadResult = await EnsureLatestAsync(targetFile,
                                                                                      targetFileTempPath,
                                                                                      logger,
                                                                                      httpClient,
                                                                                      cancellationToken).ConfigureAwait(false);

                    if (nuGetDownloadResult?.Succeeded == true)
                    {
                        return(nuGetDownloadResult);
                    }
                }

                return(result);
            }
            finally
            {
                if (ownsClient)
                {
                    httpClient.Dispose();
                }
            }
        }
        private async Task <NuGetDownloadResult?> EnsureLatestAsync(
            FileInfo targetFile,
            string targetFileTempPath,
            ILogger logger,
            HttpClient httpClient,
            CancellationToken cancellationToken)
        {
            targetFile.Refresh();

            if (!targetFile.Exists)
            {
                logger.Warning("The target nuget.exe file '{TargetFile}' does not exist, skipping latest check",
                               targetFile.FullName);
                return(null);
            }

            try
            {
                void StandardErrorAction(string message, string category)
                {
                    logger.Error("{Category} {Message}", category, message);
                }

                void DebugAction(string message, string category)
                {
                    logger.Debug("{Category} {Message}", category, message);
                }

                void VerboseAction(string message, string category)
                {
                    logger.Verbose("{Category} {Message}", category, message);
                }

                void ToolAction(string message, string category)
                {
                    logger.Verbose("{Category} {Message}", category, message);
                }

                var output = new List <string>();

                ExitCode exitCode = await ProcessRunner.ExecuteProcessAsync(targetFile.FullName,
                                                                            standardOutLog : (message, category) =>
                {
                    logger.Information("{Category} {Message}", category, message);
                    output.Add(message);
                },
                                                                            standardErrorAction : StandardErrorAction,
                                                                            debugAction : DebugAction,
                                                                            verboseAction : VerboseAction,
                                                                            toolAction : ToolAction,
                                                                            cancellationToken : cancellationToken).ConfigureAwait(false);

                if (!exitCode.IsSuccess)
                {
                    return(null);
                }

                // Found version string should be like 'NuGet Version: 4.7.1.5393'

                string?foundVersionLine = output.SingleOrDefault(line => line.Trim().StartsWith("NuGet Version:"));

                string[]? semVerParts = foundVersionLine
                                        ?.Split(':').LastOrDefault()
                                        ?.Trim()
                                        .Split('.')
                                        .Take(3).ToArray();

                if (semVerParts is null)
                {
                    logger.Warning("Could not find current nuget.exe version, could not find expected output");
                    return(null);
                }

                string mayBeVersion = string.Join(".", semVerParts);

                if (
                    !SemanticVersion.TryParse(mayBeVersion, out SemanticVersion currentVersion))
                {
                    logger.Warning("Could not find nuget.exe version from value '{PossibleVersion}'", mayBeVersion);
                    return(null);
                }

                ImmutableArray <AvailableVersion> availableVersion = await GetAvailableVersionsFromNuGet(
                    httpClient,
                    logger,
                    cancellationToken).ConfigureAwait(false);

                if (availableVersion.IsDefaultOrEmpty)
                {
                    return(null);
                }

                AvailableVersion newest = availableVersion.OrderByDescending(s => s.SemanticVersion).First();

                if (newest.SemanticVersion > currentVersion)
                {
                    logger.Debug(
                        "Newest available version found was {Newest} which is greater than the installed version {Installed}, downloading newer version",
                        newest.SemanticVersion.ToNormalizedString(),
                        currentVersion.ToNormalizedString());

                    NuGetDownloadResult newerResult = await DownloadAsync(logger,
                                                                          newest.DownloadUrl,
                                                                          targetFile,
                                                                          targetFileTempPath,
                                                                          httpClient,
                                                                          cancellationToken).ConfigureAwait(false);

                    if (newerResult.Succeeded)
                    {
                        return(newerResult);
                    }

                    logger.Warning(newerResult.Exception,
                                   "Could not download newest nuget.exe version {Version} {Result}",
                                   newest.SemanticVersion.ToNormalizedString(),
                                   newerResult.Result);
                    return(null);
                }

                logger.Debug(
                    "Newest available version found was {Newest} which is not greater than the installed version {Installed}",
                    newest.SemanticVersion.ToNormalizedString(),
                    currentVersion.ToNormalizedString());
            }
            catch (Exception ex)
            {
                logger.Warning(ex, "Could not ensure latest version is installed");
            }

            return(null);
        }