2016-07-15 15:31:50 +00:00
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
2016-11-08 23:14:47 +00:00
using System ;
2016-07-15 15:31:50 +00:00
using System.IO ;
2019-12-07 23:05:24 +00:00
using System.Net ;
2016-06-28 01:26:57 +00:00
using System.Net.Http ;
2019-12-07 23:05:24 +00:00
using System.Threading.Tasks ;
using System.Collections.Generic ;
2016-06-28 01:26:57 +00:00
using Microsoft.Build.Framework ;
using Microsoft.Build.Utilities ;
namespace Microsoft.DotNet.Cli.Build
{
2019-12-07 23:05:24 +00:00
public class DownloadFile : Microsoft . Build . Utilities . Task
2016-06-28 01:26:57 +00:00
{
[Required]
public string Uri { get ; set ; }
2019-12-07 23:05:24 +00:00
/// <summary>
/// If this field is set and the task fail to download the file from `Uri`, with a NotFound
/// status, it will try to download the file from `PrivateUri`.
/// </summary>
public string PrivateUri { get ; set ; }
public int MaxRetries { get ; set ; } = 5 ;
2016-06-28 01:26:57 +00:00
[Required]
public string DestinationPath { get ; set ; }
public bool Overwrite { get ; set ; }
public override bool Execute ( )
2019-12-07 23:05:24 +00:00
{
return ExecuteAsync ( ) . GetAwaiter ( ) . GetResult ( ) ;
}
private async System . Threading . Tasks . Task < bool > ExecuteAsync ( )
2016-06-28 01:26:57 +00:00
{
2018-10-23 02:59:58 +00:00
string destinationDir = Path . GetDirectoryName ( DestinationPath ) ;
if ( ! Directory . Exists ( destinationDir ) )
{
Directory . CreateDirectory ( destinationDir ) ;
}
2016-06-28 01:26:57 +00:00
if ( File . Exists ( DestinationPath ) & & ! Overwrite )
{
return true ;
}
2017-02-15 01:21:37 +00:00
const string FileUriProtocol = "file://" ;
2016-06-29 07:41:38 +00:00
2017-02-15 01:21:37 +00:00
if ( Uri . StartsWith ( FileUriProtocol , StringComparison . Ordinal ) )
2016-06-28 01:26:57 +00:00
{
2017-02-15 01:21:37 +00:00
var filePath = Uri . Substring ( FileUriProtocol . Length ) ;
Log . LogMessage ( $"Copying '{filePath}' to '{DestinationPath}'" ) ;
File . Copy ( filePath , DestinationPath ) ;
2019-12-07 23:05:24 +00:00
return true ;
2017-02-15 01:21:37 +00:00
}
2019-12-07 23:05:24 +00:00
List < string > errorMessages = new List < string > ( ) ;
bool? downloadStatus = await DownloadWithRetriesAsync ( Uri , DestinationPath , errorMessages ) ;
if ( downloadStatus = = false & & ! string . IsNullOrEmpty ( PrivateUri ) )
2017-02-15 01:21:37 +00:00
{
2019-12-07 23:05:24 +00:00
downloadStatus = await DownloadWithRetriesAsync ( PrivateUri , DestinationPath , errorMessages ) ;
}
2016-06-28 01:26:57 +00:00
2019-12-07 23:05:24 +00:00
if ( downloadStatus ! = true )
{
foreach ( var error in errorMessages )
2016-06-28 01:26:57 +00:00
{
2019-12-07 23:05:24 +00:00
Log . LogError ( error ) ;
}
}
return downloadStatus = = true ;
}
2017-02-15 01:21:37 +00:00
2019-12-07 23:05:24 +00:00
/// <summary>
/// Attempt to download file from `source` with retries when response error is different of FileNotFound and Success.
/// </summary>
/// <param name="source">URL to the file to be downloaded.</param>
/// <param name="target">Local path where to put the downloaded file.</param>
/// <returns>true: Download Succeeded. false: Download failed with 404. null: Download failed but is retriable.</returns>
private async Task < bool? > DownloadWithRetriesAsync ( string source , string target , List < string > errorMessages )
{
Random rng = new Random ( ) ;
Log . LogMessage ( MessageImportance . High , $"Attempting download '{source}' to '{target}'" ) ;
using ( var httpClient = new HttpClient ( ) )
{
for ( int retryNumber = 0 ; retryNumber < MaxRetries ; retryNumber + + )
{
2017-02-15 01:21:37 +00:00
try
2016-11-08 23:14:47 +00:00
{
2019-12-07 23:05:24 +00:00
var httpResponse = await httpClient . GetAsync ( source ) ;
Log . LogMessage ( MessageImportance . High , $"{source} -> {httpResponse.StatusCode}" ) ;
// The Azure Storage REST API returns '400 - Bad Request' in some cases
// where the resource is not found on the storage.
// https://docs.microsoft.com/en-us/rest/api/storageservices/common-rest-api-error-codes
if ( httpResponse . StatusCode = = HttpStatusCode . NotFound | |
httpResponse . ReasonPhrase . IndexOf ( "The requested URI does not represent any resource on the server." , StringComparison . OrdinalIgnoreCase ) = = 0 )
2017-02-15 01:21:37 +00:00
{
2019-12-07 23:05:24 +00:00
errorMessages . Add ( $"Problems downloading file from '{source}'. Does the resource exist on the storage? {httpResponse.StatusCode} : {httpResponse.ReasonPhrase}" ) ;
return false ;
2017-02-15 01:21:37 +00:00
}
2019-12-07 23:05:24 +00:00
httpResponse . EnsureSuccessStatusCode ( ) ;
using ( var outStream = File . Create ( target ) )
{
await httpResponse . Content . CopyToAsync ( outStream ) ;
}
Log . LogMessage ( MessageImportance . High , $"returning true {source} -> {httpResponse.StatusCode}" ) ;
return true ;
2017-02-15 01:21:37 +00:00
}
2019-12-07 23:05:24 +00:00
catch ( Exception e )
2017-02-15 01:21:37 +00:00
{
2019-12-07 23:05:24 +00:00
Log . LogMessage ( MessageImportance . High , $"returning error in {source} " ) ;
errorMessages . Add ( $"Problems downloading file from '{source}'. {e.Message} {e.StackTrace}" ) ;
File . Delete ( target ) ;
2016-11-08 23:14:47 +00:00
}
2019-12-07 23:05:24 +00:00
await System . Threading . Tasks . Task . Delay ( rng . Next ( 1000 , 10000 ) ) ;
2016-06-28 01:26:57 +00:00
}
}
2019-12-07 23:05:24 +00:00
Log . LogMessage ( MessageImportance . High , $"giving up {source} " ) ;
errorMessages . Add ( $"Giving up downloading the file from '{source}' after {MaxRetries} retries." ) ;
return null ;
2016-06-28 01:26:57 +00:00
}
}
}