Files
PowerShell/src/Microsoft.PackageManagement.PackageSourceListProvider/PackageList/WebDownloader.cs
T

245 lines
9.4 KiB
C#

#if !UNIX
//
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
namespace Microsoft.PackageManagement.PackageSourceListProvider
{
using System;
using System.IO;
using System.Net;
using System.Threading;
using Directory = System.IO.Directory;
using File = System.IO.File;
using System.Threading.Tasks;
using Microsoft.PackageManagement.Provider.Utility;
public class WebDownloader {
/// <summary>
/// Download data from remote via uri query.
/// </summary>
/// <param name="fileName">A file to store the downloaded data.</param>
/// <param name="query">Uri query</param>
/// <param name="request">An object passed in from the PackageManagement platform that contains APIs that can be used to interact with it </param>
/// <param name="networkCredential">Credential to pass along to get httpclient</param>
/// <param name="progressTracker">Utility class to help track progress</param>
/// <returns></returns>
internal static async Task<long> DownloadDataToFileAsync(string fileName, string query, PackageSourceListRequest request, NetworkCredential networkCredential, ProgressTracker progressTracker)
{
request.Verbose(Resources.Messages.DownloadingPackage, query);
// try downloading for 3 times
int remainingTry = 3;
long totalDownloaded = 0;
CancellationTokenSource cts;
Stream input = null;
FileStream output = null;
while (remainingTry > 0)
{
// if user cancel the request, no need to do anything
if (request.IsCanceled)
{
break;
}
input = null;
output = null;
cts = new CancellationTokenSource();
totalDownloaded = 0;
try
{
// decrease try by 1
remainingTry -= 1;
var httpClient = request.Client;
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "text/html; charset=iso-8859-1");
input = await httpClient.GetStreamAsync(query);
// buffer size of 64 KB, this seems to be preferable buffer size, not too small and not too big
byte[] bytes = new byte[1024 * 64];
output = File.Open(fileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read);
int current = 0;
// here we read content that we got from the http response stream into the bytes array
current = await input.ReadAsync(bytes, 0, bytes.Length, cts.Token);
// report initial progress
request.Progress(progressTracker.ProgressID, progressTracker.StartPercent, Resources.Messages.BytesRead, current);
while (current > 0)
{
totalDownloaded += current;
// here we write out the bytes array into the file
await output.WriteAsync(bytes, 0, current, cts.Token);
// report the progress
request.Progress(progressTracker.ProgressID, progressTracker.StartPercent, Resources.Messages.BytesRead, totalDownloaded);
// continue reading from the stream
current = await input.ReadAsync(bytes, 0, bytes.Length, cts.Token);
}
if (totalDownloaded > 0)
{
// report that we finished the download
request.Progress(progressTracker.ProgressID, progressTracker.EndPercent, Resources.Messages.BytesRead, totalDownloaded);
request.CompleteProgress(progressTracker.ProgressID, true);
return totalDownloaded;
}
// if request is canceled, don't retry
if (request.IsCanceled)
{
return 0;
}
}
catch (Exception ex)
{
request.CompleteProgress(progressTracker.ProgressID, true);
request.Verbose(ex.Message);
request.Debug(ex.StackTrace);
}
finally
{
// dispose input and output stream
if (input != null)
{
input.Dispose();
}
// dispose it
if (output != null)
{
output.Dispose();
}
// delete the file if created and nothing has downloaded
if (totalDownloaded == 0 && File.Exists(fileName))
{
fileName.TryHardToDelete();
}
if (cts != null)
{
cts.Dispose();
}
}
// we have to retry again
request.Verbose(Resources.Messages.RetryingDownload, query, remainingTry);
}
return totalDownloaded;
}
internal static string DownloadFile(string queryUrl, string destination, PackageSourceListRequest request, ProgressTracker progressTracker)
{
try
{
request.Debug(Resources.Messages.DebugInfoCallMethod, Constants.ProviderName, string.Format(System.Globalization.CultureInfo.InvariantCulture, "DownloadFile - url='{0}', destination='{1}'", queryUrl, destination));
if (string.IsNullOrWhiteSpace(destination))
{
throw new ArgumentNullException("destination");
}
// make sure that the parent folder is created first.
var folder = Path.GetDirectoryName(destination);
if (!Directory.Exists(folder))
{
Directory.CreateDirectory(folder);
}
if (File.Exists(destination))
{
destination.TryHardToDelete();
}
if (progressTracker == null)
{
progressTracker = new ProgressTracker(request.StartProgress(0, Resources.Messages.DownloadingPackage, queryUrl));
}
Uri uri;
if (!Uri.TryCreate(queryUrl, UriKind.Absolute, out uri))
{
request.Error(Internal.ErrorCategory.InvalidOperation, Resources.Messages.UnsuportedUriFormat, Constants.ProviderName, queryUrl);
return null;
}
if (uri.IsFile)
{
// downloading from a file share
using (var input = File.OpenRead(queryUrl))
{
using (var output = new FileStream(destination, FileMode.Create, FileAccess.Write, FileShare.Read))
{
request.Progress(progressTracker.ProgressID, progressTracker.StartPercent, Resources.Messages.Downloading);
input.CopyTo(output);
}
}
request.CompleteProgress(progressTracker.ProgressID, true);
}
else
{
//Downloading from url
var result = DownloadDataToFileAsync(destination, queryUrl, request, PathUtility.GetNetworkCredential(request.CredentialUsername, request.CredentialPassword), progressTracker).Result;
}
if (File.Exists(destination))
{
request.Verbose(Resources.Messages.CompletedDownload, queryUrl);
return destination;
}
else
{
request.Error(Internal.ErrorCategory.InvalidOperation, Resources.Messages.FailedToDownload, Constants.ProviderName, queryUrl, destination);
return null;
}
}
catch (Exception ex)
{
ex.Dump(request);
request.Warning(ex.Message);
return null;
}
}
internal static bool PerformSecurityScan(string fileFullPath)
{
//TODO need to do security scan before running install
return true;
}
}
}
#endif