You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
456 lines
15 KiB
456 lines
15 KiB
using System;
|
|
using System.IO;
|
|
using System.Net;
|
|
using System.Net.Http;
|
|
using System.Threading;
|
|
|
|
namespace QYMessageCenter.Common.Http
|
|
{
|
|
/// <summary>
|
|
/// Http下载器
|
|
/// </summary>
|
|
public class HttpDownloader
|
|
{
|
|
|
|
private IHttpClientFactory httpClientFactory;
|
|
|
|
public HttpDownloader(IHttpClientFactory httpClientFactory, DownloadSetting dwSetting = null)
|
|
{
|
|
this.httpClientFactory = httpClientFactory;
|
|
complateArgs = new DownloadCompletedEventArgs();
|
|
changeArgs = new DownloadProgressChangedEventArgs();
|
|
if (dwSetting == null)
|
|
dwSetting = new DownloadSetting();
|
|
downloadSetting = dwSetting;
|
|
buffer = new byte[downloadSetting.BufferSize];
|
|
}
|
|
|
|
~HttpDownloader()
|
|
{
|
|
this.buffer = null;
|
|
this.downloadSetting = null;
|
|
this.complateArgs.Error = null;
|
|
this.complateArgs = null;
|
|
this.changeArgs = null;
|
|
}
|
|
|
|
#region Property
|
|
|
|
/// <summary>
|
|
/// 下载进度变化参数
|
|
/// </summary>
|
|
private DownloadProgressChangedEventArgs changeArgs;
|
|
|
|
/// <summary>
|
|
/// 下载完成参数
|
|
/// </summary>
|
|
private DownloadCompletedEventArgs complateArgs;
|
|
|
|
/// <summary>
|
|
/// 下载参数配置类
|
|
/// </summary>
|
|
private DownloadSetting downloadSetting;
|
|
|
|
/// <summary>
|
|
/// 下载缓冲区
|
|
/// </summary>
|
|
private byte[] buffer;
|
|
|
|
#endregion
|
|
|
|
#region Delegate
|
|
public delegate void DownloadProgressChangedEventHandler(object sender, DownloadProgressChangedEventArgs e);
|
|
|
|
public delegate void DownloadCompletedEventHandler(object sender, DownloadCompletedEventArgs e);
|
|
|
|
public delegate void ReDownloadEventHandler(object sender, DownloadCompletedEventArgs e);
|
|
#endregion
|
|
|
|
#region Event
|
|
/// <summary>
|
|
/// 下载进度变化事件
|
|
/// </summary>
|
|
public event DownloadProgressChangedEventHandler OnDownloadProgressChanged;
|
|
|
|
/// <summary>
|
|
/// 下载完成事件
|
|
/// </summary>
|
|
public event DownloadCompletedEventHandler OnDownloadComplated;
|
|
|
|
/// <summary>
|
|
/// 自动重下事件
|
|
/// </summary>
|
|
public event ReDownloadEventHandler OnReDownload;
|
|
#endregion
|
|
|
|
#region Method
|
|
/// <summary>
|
|
/// 设置下载参数
|
|
/// </summary>
|
|
/// <param name="dwSetting"></param>
|
|
public void SetDownloadSetting(DownloadSetting dwSetting)
|
|
{
|
|
if (dwSetting != null)
|
|
downloadSetting = dwSetting;
|
|
}
|
|
|
|
private void DoProcessChangedEvent(DownloadProgressChangedEventArgs args)
|
|
{
|
|
OnDownloadProgressChanged?.Invoke(this, args);
|
|
}
|
|
|
|
private void DoCompletedEvent(DownloadCompletedEventArgs args)
|
|
{
|
|
OnDownloadComplated?.Invoke(this, args);
|
|
}
|
|
|
|
private void DoReDownloadEvent(DownloadCompletedEventArgs args)
|
|
{
|
|
OnReDownload?.Invoke(this, args);
|
|
}
|
|
|
|
private void ArgsInit()
|
|
{
|
|
changeArgs.Cancel = false;
|
|
complateArgs.Cancelled = false;
|
|
complateArgs.Error = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取文件总大小
|
|
/// </summary>
|
|
/// <param name="url"></param>
|
|
/// <returns></returns>
|
|
public long GetTotalLength(string url)
|
|
{
|
|
long length = 0;
|
|
try
|
|
{
|
|
using (var httpClient = httpClientFactory.CreateClient())
|
|
{
|
|
var requestMessage = new HttpRequestMessage(HttpMethod.Head, url);
|
|
var httpResponse = httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead).Result;
|
|
if (httpResponse.IsSuccessStatusCode)
|
|
length = httpResponse.Content.Headers.ContentLength ?? 0;
|
|
}
|
|
|
|
//var req = (HttpWebRequest)WebRequest.Create(new Uri(url));
|
|
//req.Method = "HEAD";
|
|
//req.Timeout = 5000;
|
|
//var res = (HttpWebResponse)req.GetResponse();
|
|
//if (res.StatusCode == HttpStatusCode.OK)
|
|
//{
|
|
// length = res.ContentLength;
|
|
//}
|
|
//res.Close();
|
|
return length;
|
|
}
|
|
catch (WebException wex)
|
|
{
|
|
throw wex;
|
|
}
|
|
}
|
|
|
|
|
|
private bool DownloadFile(string url, string savePath, long startPosition, long totalSize)
|
|
{
|
|
long currentReadSize = 0; //当前已经读取的字节数
|
|
if (startPosition != 0)
|
|
currentReadSize = startPosition;
|
|
using (var httpClient = httpClientFactory.CreateClient())
|
|
{
|
|
try
|
|
{
|
|
httpClient.Timeout = new TimeSpan(0, 0, 20);
|
|
if (currentReadSize != 0 && currentReadSize == totalSize)
|
|
{
|
|
changeArgs.CurrentSize = changeArgs.TotalSize = totalSize;
|
|
return true;
|
|
}
|
|
if (currentReadSize != 0)
|
|
{
|
|
try
|
|
{
|
|
httpClient.DefaultRequestHeaders.Range = new System.Net.Http.Headers.RangeHeaderValue(currentReadSize, totalSize);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
//http 416
|
|
currentReadSize = 0;
|
|
}
|
|
}
|
|
|
|
using (var response = httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead).Result)
|
|
{
|
|
using (var responseStream = response.Content.ReadAsStreamAsync().Result)
|
|
{
|
|
changeArgs.TotalSize = totalSize;
|
|
using (var fs = new FileStream(savePath, currentReadSize == 0 ? FileMode.Create : FileMode.Append, FileAccess.Write))
|
|
{
|
|
//判断服务器是否支持断点续下
|
|
if (currentReadSize != 0 &&
|
|
response.StatusCode == HttpStatusCode.PartialContent &&
|
|
response.Content.Headers.ContentLength != totalSize)
|
|
fs.Seek(startPosition, SeekOrigin.Begin); //支持,从本地文件流末尾进行写入
|
|
else
|
|
currentReadSize = 0; //不支持,从本地文件流开始进行写入
|
|
|
|
if (buffer.Length != downloadSetting.BufferSize)
|
|
buffer = new byte[downloadSetting.BufferSize];
|
|
int size = responseStream.Read(buffer, 0, buffer.Length);
|
|
while (size != 0)
|
|
{
|
|
fs.Write(buffer, 0, size);
|
|
if (buffer.Length != downloadSetting.BufferSize)
|
|
buffer = new byte[downloadSetting.BufferSize];
|
|
size = responseStream.Read(buffer, 0, buffer.Length);
|
|
currentReadSize += size; //累计下载大小
|
|
//执行下载进度变化事件
|
|
changeArgs.CurrentSize = currentReadSize;
|
|
DoProcessChangedEvent(changeArgs);
|
|
//判断挂起状态
|
|
if (changeArgs.Cancel)
|
|
return true;
|
|
}
|
|
|
|
//执行下载进度变化事件
|
|
changeArgs.CurrentSize = changeArgs.TotalSize;
|
|
DoProcessChangedEvent(changeArgs);
|
|
fs.Flush(true);
|
|
}
|
|
|
|
//验证远程服务器文件字节数和本地文件字节数是否一致
|
|
long localFileSize = 0;
|
|
try
|
|
{
|
|
localFileSize = new FileInfo(savePath).Length;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
localFileSize = changeArgs.CurrentSize;
|
|
}
|
|
|
|
if (totalSize != localFileSize)
|
|
{
|
|
complateArgs.Error = new Exception(string.Format("远程文件字节:[{0}]与本地文件字节:[{1}]不一致", totalSize, localFileSize));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (IOException ex)
|
|
{
|
|
complateArgs.Error = ex;
|
|
}
|
|
catch (WebException ex)
|
|
{
|
|
complateArgs.Error = ex;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
complateArgs.Error = ex;
|
|
}
|
|
}
|
|
return complateArgs.Error == null;
|
|
}
|
|
|
|
|
|
public bool DownloadFile(string url, string saveFolderPath, string saveFileName, long? totalSize)
|
|
{
|
|
ArgsInit();
|
|
var result = false;
|
|
//验证文件夹
|
|
try
|
|
{
|
|
if (!Directory.Exists(saveFolderPath))
|
|
Directory.CreateDirectory(saveFolderPath);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
complateArgs.Error = ex;
|
|
DoCompletedEvent(complateArgs);
|
|
return result;
|
|
}
|
|
|
|
if (totalSize == null || totalSize.Value == 0)
|
|
{
|
|
try
|
|
{
|
|
totalSize = GetTotalLength(url);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine("获取远程服务器字节数失败 {0}", ex.Message);
|
|
complateArgs.Error = ex;
|
|
DoCompletedEvent(complateArgs);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
string saveFilePath = Path.Combine(saveFolderPath, saveFileName);
|
|
long startPosition = 0;
|
|
for (var i = 1; i <= downloadSetting.TryCount; i++)
|
|
{
|
|
if (File.Exists(saveFilePath))
|
|
{
|
|
try
|
|
{
|
|
startPosition = new FileInfo(saveFilePath).Length;
|
|
}
|
|
catch
|
|
{
|
|
startPosition = 0;
|
|
}
|
|
}
|
|
|
|
result = DownloadFile(url, saveFilePath, startPosition, totalSize.Value);
|
|
if (result)
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
if (i != downloadSetting.TryCount)
|
|
{
|
|
complateArgs.TryCount = i + 1;
|
|
DoReDownloadEvent(complateArgs);
|
|
Thread.Sleep(downloadSetting.SleepTime);
|
|
if (complateArgs.Cancelled)
|
|
break;
|
|
ArgsInit();
|
|
}
|
|
}
|
|
if (complateArgs.Cancelled)
|
|
break;
|
|
}
|
|
DoCompletedEvent(complateArgs);
|
|
return result;
|
|
}
|
|
|
|
public byte[] DwonloadBytes(string url)
|
|
{
|
|
ArgsInit();
|
|
using (var httpClient = httpClientFactory.CreateClient())
|
|
{
|
|
return httpClient.GetByteArrayAsync(url).Result;
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// 取消/暂停下载
|
|
/// </summary>
|
|
public void CancelDownload()
|
|
{
|
|
this.changeArgs.Cancel = true;
|
|
this.complateArgs.Cancelled = true;
|
|
}
|
|
#endregion
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// 下载进度变化参数
|
|
/// </summary>
|
|
public class DownloadProgressChangedEventArgs
|
|
{
|
|
public DownloadProgressChangedEventArgs()
|
|
{
|
|
ProgressPercentage = 0.0;
|
|
Cancel = false;
|
|
}
|
|
/// <summary>
|
|
/// 下载进度百分比
|
|
/// </summary>
|
|
public double ProgressPercentage;
|
|
|
|
|
|
private long currentSize;
|
|
|
|
/// <summary>
|
|
/// 当前下载总大小
|
|
/// </summary>
|
|
public long CurrentSize
|
|
{
|
|
get { return currentSize; }
|
|
set
|
|
{
|
|
currentSize = value;
|
|
this.ProgressPercentage = Math.Round((CurrentSize * 1.0 / TotalSize) * 100, 2);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 文件总大小
|
|
/// </summary>
|
|
public long TotalSize;
|
|
|
|
/// <summary>
|
|
/// 取消状态
|
|
/// </summary>
|
|
public bool Cancel;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 下载完成参数
|
|
/// </summary>
|
|
public class DownloadCompletedEventArgs
|
|
{
|
|
public DownloadCompletedEventArgs()
|
|
{
|
|
Cancelled = false;
|
|
Error = null;
|
|
}
|
|
/// <summary>
|
|
/// 下载异常
|
|
/// </summary>
|
|
public Exception Error;
|
|
|
|
/// <summary>
|
|
/// 重试次数
|
|
/// </summary>
|
|
public int TryCount;
|
|
|
|
/// <summary>
|
|
/// 下载操作是否被取消 【取消则为true;默认false】
|
|
/// </summary>
|
|
public bool Cancelled;
|
|
}
|
|
|
|
public class DownloadSetting
|
|
{
|
|
public DownloadSetting()
|
|
{
|
|
this.BufferSize = 1024 * 1024 * 1;
|
|
this.TryCount = 5;
|
|
this.SleepTime = 5000;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="bufferSize"></param>
|
|
/// <param name="tryCount"></param>
|
|
/// <param name="sleepTime">毫秒</param>
|
|
public DownloadSetting(int bufferSize, int tryCount, int sleepTime)
|
|
{
|
|
this.BufferSize = bufferSize;
|
|
this.TryCount = tryCount;
|
|
this.SleepTime = sleepTime;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 下载缓冲区大小
|
|
/// </summary>
|
|
public int BufferSize;
|
|
|
|
/// <summary>
|
|
/// 重试次数
|
|
/// </summary>
|
|
public int TryCount;
|
|
|
|
/// <summary>
|
|
/// 自动重下休眠时间, 毫秒
|
|
/// </summary>
|
|
public int SleepTime;
|
|
}
|
|
}
|
|
|