File: Utils.cs
Web Access
Project: src\src\deployment-tools\src\Microsoft.Deployment.DotNet.Releases\src\Microsoft.Deployment.DotNet.Releases.csproj (Microsoft.Deployment.DotNet.Releases)
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Threading.Tasks;

#pragma warning disable CA1810

namespace Microsoft.Deployment.DotNet.Releases
{
    /// <summary>
    /// Utility and helper methods.
    /// </summary>
    internal class Utils
    {
        internal static readonly HttpClient s_httpClient;

        /// <summary>
        /// Determines if a local file is the latest version compared to an online copy.
        /// </summary>
        /// <param name="fileName">The path of the local file.</param>
        /// <param name="address">The address pointing of the file.</param>
        /// <returns><see langword="true"/> if the local file is the latest; <see langword="false"/> otherwise.</returns>
        internal static async Task<bool> IsLatestFileAsync(string fileName, Uri address)
        {
            var httpRequest = new HttpRequestMessage(HttpMethod.Head, address);
            HttpResponseMessage httpResponse = await s_httpClient.SendAsync(httpRequest).ConfigureAwait(false);

            httpResponse.EnsureSuccessStatusCode();

            DateTime? onlineLastModified = httpResponse.Content.Headers.LastModified?.DateTime;
            var fileInfo = new FileInfo(fileName);

            return fileInfo.LastWriteTime >= onlineLastModified;
        }

        /// <summary>
        /// Downloads or copy a file from the specified address to the specified destination.
        /// </summary>
        /// <param name="address">The address of the source file to download or copy.</param>
        /// <param name="fileName">The path of the destination file.</param>
        /// <returns>The task object representing the asynchronous operation.</returns>
        internal static async Task DownloadFileAsync(Uri address, string fileName)
        {
            string directory = Path.GetDirectoryName(fileName);

            if (!Directory.Exists(directory))
            {
                Directory.CreateDirectory(directory);
            }

            if (address.Scheme == Uri.UriSchemeFile)
            {
                using Stream source = File.Open(address.LocalPath, FileMode.Open, FileAccess.Read, FileShare.Read);
                using Stream destination = File.Create(fileName);
                await source.CopyToAsync(destination).ConfigureAwait(false);
            }
            else
            {
                HttpResponseMessage httpResponse = await s_httpClient.GetAsync(address).ConfigureAwait(false);
                httpResponse.EnsureSuccessStatusCode();

                using FileStream stream = File.Create(fileName);
                await httpResponse.Content.CopyToAsync(stream).ConfigureAwait(false);
            }
        }

        /// <summary>
        /// Computes the hash for the specified file using the specified hash algorithm.
        /// </summary>
        /// <param name="fileName">The path, including the filename and extension of the file to use.</param>
        /// <param name="hashAlgorithm">The hash algorithm to use.</param>
        /// <returns>A string containing the file hash.</returns>
        /// <exception cref="ArgumentNullException"/>
        /// <exception cref="ArgumentException" />
        /// <exception cref="FileNotFoundException" />
        internal static string GetFileHash(string fileName, HashAlgorithm hashAlgorithm)
        {
            if (fileName is null)
            {
                throw new ArgumentNullException(nameof(fileName));
            }

            if (fileName == string.Empty)
            {
                throw new ArgumentException(ReleasesResources.ValueCannotBeEmpty, nameof(fileName));
            }

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

            if (!File.Exists(fileName))
            {
                throw new FileNotFoundException(string.Format(ReleasesResources.FileNotFound, fileName));
            }

            using FileStream stream = File.OpenRead(fileName);
            byte[] checksum = hashAlgorithm.ComputeHash(stream);

            return BitConverter.ToString(checksum).Replace("-", "").ToLowerInvariant();
        }

        /// <summary>
        /// Checks whether a specified file exists, and if not, optionally downloads a copy from
        /// the specified address.
        /// </summary>
        /// <param name="path">The path of the local file to check.</param>
        /// <param name="downloadLatest">When <see langword="true"/>, the latest copy of the file is downloaded if a newer version
        /// exists online.</param>
        /// <param name="address">The address of the file to download.</param>
        /// <returns>The task object representing the asynchronous operation.</returns>
        internal static async Task GetLatestFileAsync(string path, bool downloadLatest, Uri address)
        {
            if (path is null)
            {
                throw new ArgumentNullException(nameof(path));
            }

            if (address is null)
            {
                throw new ArgumentNullException(nameof(address));
            }

            if (path == string.Empty)
            {
                throw new ArgumentException(ReleasesResources.ValueCannotBeEmpty, nameof(path));
            }

            if (!File.Exists(path))
            {
                if (!downloadLatest)
                {
                    throw new FileNotFoundException(string.Format(ReleasesResources.FileNotFound, path));
                }

                await DownloadFileAsync(address, path).ConfigureAwait(false);
            }
            else if (downloadLatest && !await IsLatestFileAsync(path, address).ConfigureAwait(false))
            {
                await DownloadFileAsync(address, path).ConfigureAwait(false);
            }
        }

        static Utils()
        {
            s_httpClient = new HttpClient();
            s_httpClient.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue
            {
                NoCache = true
            };
        }
    }
}