375 lines
15 KiB
C#
375 lines
15 KiB
C#
|
|
using System.Net;
|
|||
|
|
using System.IO;
|
|||
|
|
|
|||
|
|
namespace Zerolauncher.Manager
|
|||
|
|
{
|
|||
|
|
|
|||
|
|
public class DownloadManager
|
|||
|
|
{
|
|||
|
|
/// <summary>
|
|||
|
|
/// 主线程
|
|||
|
|
/// </summary>
|
|||
|
|
private SynchronizationContext _mainThreadSynContext;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 下载网址
|
|||
|
|
/// </summary>
|
|||
|
|
public string Url;
|
|||
|
|
|
|||
|
|
public event Action<Exception> OnError;
|
|||
|
|
|
|||
|
|
private static object errorlock = new object();
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 主要用于关闭线程
|
|||
|
|
/// </summary>
|
|||
|
|
private bool _isDownload = false;
|
|||
|
|
public DownloadManager(string url)
|
|||
|
|
{
|
|||
|
|
// 主线程赋值
|
|||
|
|
_mainThreadSynContext = SynchronizationContext.Current;
|
|||
|
|
// 突破Http协议的并发连接数限制
|
|||
|
|
ServicePointManager.DefaultConnectionLimit = 512;
|
|||
|
|
|
|||
|
|
Url = url;
|
|||
|
|
}
|
|||
|
|
/// <summary>
|
|||
|
|
/// 查询文件大小
|
|||
|
|
/// </summary>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
public long GetFileSize()
|
|||
|
|
{
|
|||
|
|
HttpWebRequest request;
|
|||
|
|
HttpWebResponse response;
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
request = (HttpWebRequest)WebRequest.CreateHttp(new Uri(Url));
|
|||
|
|
request.Method = "HEAD";
|
|||
|
|
response = (HttpWebResponse)request.GetResponse();
|
|||
|
|
// 获得文件长度
|
|||
|
|
long contentLength = response.ContentLength;
|
|||
|
|
|
|||
|
|
response.Close();
|
|||
|
|
request.Abort();
|
|||
|
|
|
|||
|
|
return contentLength;
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
onError(ex);
|
|||
|
|
// throw;
|
|||
|
|
return -1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
/// <summary>
|
|||
|
|
/// 异步查询文件大小
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="onTrigger"></param>
|
|||
|
|
public void GetFileSizeAsyn(Action<long> onTrigger = null)
|
|||
|
|
{
|
|||
|
|
ThreadStart threadStart = new ThreadStart(() =>
|
|||
|
|
{
|
|||
|
|
PostMainThreadAction<long>(onTrigger, GetFileSize());
|
|||
|
|
});
|
|||
|
|
Thread thread = new Thread(threadStart);
|
|||
|
|
thread.Start();
|
|||
|
|
}
|
|||
|
|
/// <summary>
|
|||
|
|
/// 多线程下载文件至本地
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="threadCount">线程总数</param>
|
|||
|
|
/// <param name="filePath">保存文件路径</param>
|
|||
|
|
/// <param name="onDownloading">下载过程回调(已下载文件大小、总文件大小)</param>
|
|||
|
|
/// <param name="onTrigger">下载完毕回调(下载文件数据)</param>
|
|||
|
|
public void DownloadToFile(int threadCount, string filePath, Action<long, long> onDownloading = null, Action<byte[]> onTrigger = null)
|
|||
|
|
{
|
|||
|
|
_isDownload = true;
|
|||
|
|
|
|||
|
|
long csize = 0; //已下载大小
|
|||
|
|
int ocnt = 0; //完成线程数
|
|||
|
|
|
|||
|
|
|
|||
|
|
// 下载逻辑
|
|||
|
|
GetFileSizeAsyn((size) =>
|
|||
|
|
{
|
|||
|
|
if (size == -1) return;
|
|||
|
|
// 准备工作
|
|||
|
|
var tempFilePaths = new string[threadCount];
|
|||
|
|
var tempFileFileStreams = new FileStream[threadCount];
|
|||
|
|
var dirPath = Path.GetDirectoryName(filePath);
|
|||
|
|
var fileName = Path.GetFileName(filePath);
|
|||
|
|
// 下载根目录不存在则创建
|
|||
|
|
if (!Directory.Exists(dirPath))
|
|||
|
|
{
|
|||
|
|
Directory.CreateDirectory(dirPath);
|
|||
|
|
}
|
|||
|
|
// 查看下载临时文件是否可以继续断点续传
|
|||
|
|
var fileInfos = new DirectoryInfo(dirPath).GetFiles(fileName + "*.temp");
|
|||
|
|
if (fileInfos.Length != threadCount)
|
|||
|
|
{
|
|||
|
|
// 下载临时文件数量不相同,则清理
|
|||
|
|
foreach (var info in fileInfos)
|
|||
|
|
{
|
|||
|
|
info.Delete();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// 创建下载临时文件,并创建文件流
|
|||
|
|
for (int i = 0; i < threadCount; i++)
|
|||
|
|
{
|
|||
|
|
tempFilePaths[i] = filePath + i + ".temp";
|
|||
|
|
if (!File.Exists(tempFilePaths[i]))
|
|||
|
|
{
|
|||
|
|
File.Create(tempFilePaths[i]).Dispose();
|
|||
|
|
}
|
|||
|
|
tempFileFileStreams[i] = File.OpenWrite(tempFilePaths[i]);
|
|||
|
|
tempFileFileStreams[i].Seek(tempFileFileStreams[i].Length, System.IO.SeekOrigin.Current);
|
|||
|
|
|
|||
|
|
csize += tempFileFileStreams[i].Length;
|
|||
|
|
}
|
|||
|
|
// 单线程下载过程回调函数
|
|||
|
|
Action<int, long, byte[], byte[]> t_onDownloading = (index, rsize, rbytes, data) =>
|
|||
|
|
{
|
|||
|
|
csize += rsize;
|
|||
|
|
tempFileFileStreams[index].Write(rbytes, 0, (int)rsize);
|
|||
|
|
PostMainThreadAction<long, long>(onDownloading, csize, size);
|
|||
|
|
};
|
|||
|
|
// 单线程下载完毕回调函数
|
|||
|
|
Action<int, byte[]> t_onTrigger = (index, data) =>
|
|||
|
|
{
|
|||
|
|
// 关闭文件流
|
|||
|
|
tempFileFileStreams[index].Close();
|
|||
|
|
ocnt++;
|
|||
|
|
if (ocnt >= threadCount)
|
|||
|
|
{
|
|||
|
|
// 将临时文件转为下载文件
|
|||
|
|
if (!File.Exists(filePath))
|
|||
|
|
{
|
|||
|
|
File.Create(filePath).Dispose();
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
File.WriteAllBytes(filePath, new byte[] { });
|
|||
|
|
}
|
|||
|
|
FileStream fs = File.OpenWrite(filePath);
|
|||
|
|
fs.Seek(fs.Length, System.IO.SeekOrigin.Current);
|
|||
|
|
foreach (var tempPath in tempFilePaths)
|
|||
|
|
{
|
|||
|
|
var tempData = File.ReadAllBytes(tempPath);
|
|||
|
|
fs.Write(tempData, 0, tempData.Length);
|
|||
|
|
File.Delete(tempPath);
|
|||
|
|
}
|
|||
|
|
fs.Close();
|
|||
|
|
PostMainThreadAction<byte[]>(onTrigger, File.ReadAllBytes(filePath));
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
// 分割文件尺寸,多线程下载
|
|||
|
|
long[] sizes = SplitFileSize(size, threadCount);
|
|||
|
|
for (int i = 0; i < sizes.Length; i = i + 2)
|
|||
|
|
{
|
|||
|
|
long from = sizes[i];
|
|||
|
|
long to = sizes[i + 1];
|
|||
|
|
|
|||
|
|
// 断点续传
|
|||
|
|
from += tempFileFileStreams[i / 2].Length;
|
|||
|
|
if (from >= to)
|
|||
|
|
{
|
|||
|
|
t_onTrigger(i / 2, null);
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_threadDownload(i / 2, from, to, t_onDownloading, t_onTrigger);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
/// <summary>
|
|||
|
|
/// 多线程下载文件至内存
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="threadCount">线程总数</param>
|
|||
|
|
/// <param name="onDownloading">下载过程回调(已下载文件大小、总文件大小)</param>
|
|||
|
|
/// <param name="onTrigger">下载完毕回调(下载文件数据)</param>
|
|||
|
|
public void DownloadToMemory(int threadCount, Action<long, long> onDownloading = null, Action<byte[]> onTrigger = null)
|
|||
|
|
{
|
|||
|
|
_isDownload = true;
|
|||
|
|
|
|||
|
|
long csize = 0; // 已下载大小
|
|||
|
|
int ocnt = 0; // 完成线程数
|
|||
|
|
byte[] cdata; // 已下载数据
|
|||
|
|
// 下载逻辑
|
|||
|
|
GetFileSizeAsyn((size) =>
|
|||
|
|
{
|
|||
|
|
cdata = new byte[size];
|
|||
|
|
// 单线程下载过程回调函数
|
|||
|
|
Action<int, long, byte[], byte[]> t_onDownloading = (index, rsize, rbytes, data) =>
|
|||
|
|
{
|
|||
|
|
csize += rsize;
|
|||
|
|
PostMainThreadAction<long, long>(onDownloading, csize, size);
|
|||
|
|
};
|
|||
|
|
// 单线程下载完毕回调函数
|
|||
|
|
Action<int, byte[]> t_onTrigger = (index, data) =>
|
|||
|
|
{
|
|||
|
|
long dIndex = (long)Math.Ceiling((double)(size * index / threadCount));
|
|||
|
|
Array.Copy(data, 0, cdata, dIndex, data.Length);
|
|||
|
|
|
|||
|
|
ocnt++;
|
|||
|
|
if (ocnt >= threadCount)
|
|||
|
|
{
|
|||
|
|
PostMainThreadAction<byte[]>(onTrigger, cdata);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
// 分割文件尺寸,多线程下载
|
|||
|
|
long[] sizes = SplitFileSize(size, threadCount);
|
|||
|
|
for (int i = 0; i < sizes.Length; i = i + 2)
|
|||
|
|
{
|
|||
|
|
long from = sizes[i];
|
|||
|
|
long to = sizes[i + 1];
|
|||
|
|
_threadDownload(i / 2, from, to, t_onDownloading, t_onTrigger);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
/// <summary>
|
|||
|
|
/// 单线程下载
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="index">线程ID</param>
|
|||
|
|
/// <param name="from">下载起始位置</param>
|
|||
|
|
/// <param name="to">下载结束位置</param>
|
|||
|
|
/// <param name="onDownloading">下载过程回调(线程ID、单次下载数据大小、单次下载数据缓存区、已下载文件数据)</param>
|
|||
|
|
/// <param name="onTrigger">下载完毕回调(线程ID、下载文件数据)</param>
|
|||
|
|
private void _threadDownload(int index, long from, long to, Action<int, long, byte[], byte[]> onDownloading = null, Action<int, byte[]> onTrigger = null)
|
|||
|
|
{
|
|||
|
|
Thread thread = new Thread(new ThreadStart(() =>
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var request = (HttpWebRequest)WebRequest.Create(new Uri(Url));
|
|||
|
|
request.AddRange(from, to);
|
|||
|
|
|
|||
|
|
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
|
|||
|
|
Stream ns = response.GetResponseStream();
|
|||
|
|
|
|||
|
|
byte[] rbytes = new byte[8 * 1024];
|
|||
|
|
int rSize = 0;
|
|||
|
|
MemoryStream ms = new MemoryStream();
|
|||
|
|
while (true)
|
|||
|
|
{
|
|||
|
|
if (!_isDownload) return;
|
|||
|
|
rSize = ns.Read(rbytes, 0, rbytes.Length);
|
|||
|
|
if (rSize <= 0) break;
|
|||
|
|
ms.Write(rbytes, 0, rSize);
|
|||
|
|
if (onDownloading != null) onDownloading(index, rSize, rbytes, ms.ToArray());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
ns.Close();
|
|||
|
|
response.Close();
|
|||
|
|
request.Abort();
|
|||
|
|
|
|||
|
|
if (ms.Length == (to - from) + 1)
|
|||
|
|
{
|
|||
|
|
if (onTrigger != null) onTrigger(index, ms.ToArray());
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
lock (errorlock)
|
|||
|
|
{
|
|||
|
|
if (_isDownload)
|
|||
|
|
{
|
|||
|
|
onError(new Exception("文件大小校验不通过"));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
onError(ex);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
}));
|
|||
|
|
thread.Start();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void Close()
|
|||
|
|
{
|
|||
|
|
_isDownload = false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 分割文件大小
|
|||
|
|
/// </summary>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
private long[] SplitFileSize(long size, int count)
|
|||
|
|
{
|
|||
|
|
long[] result = new long[count * 2];
|
|||
|
|
for (int i = 0; i < count; i++)
|
|||
|
|
{
|
|||
|
|
long from = (long)Math.Ceiling((double)(size * i / count));
|
|||
|
|
long to = (long)Math.Ceiling((double)(size * (i + 1) / count)) - 1;
|
|||
|
|
result[i * 2] = from;
|
|||
|
|
result[i * 2 + 1] = to;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void onError(Exception ex)
|
|||
|
|
{
|
|||
|
|
Close();
|
|||
|
|
PostMainThreadAction<Exception>(OnError, ex);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 通知主线程回调
|
|||
|
|
/// </summary>
|
|||
|
|
private void PostMainThreadAction(Action action)
|
|||
|
|
{
|
|||
|
|
_mainThreadSynContext.Post(new SendOrPostCallback((o) =>
|
|||
|
|
{
|
|||
|
|
Action e = (Action)o.GetType().GetProperty("action").GetValue(o);
|
|||
|
|
if (e != null) e();
|
|||
|
|
}), new { action = action });
|
|||
|
|
}
|
|||
|
|
private void PostMainThreadAction<T>(Action<T> action, T arg1)
|
|||
|
|
{
|
|||
|
|
_mainThreadSynContext.Post(new SendOrPostCallback((o) =>
|
|||
|
|
{
|
|||
|
|
Action<T> e = (Action<T>)o.GetType().GetProperty("action").GetValue(o);
|
|||
|
|
T t1 = (T)o.GetType().GetProperty("arg1").GetValue(o);
|
|||
|
|
if (e != null) e(t1);
|
|||
|
|
}), new { action = action, arg1 = arg1 });
|
|||
|
|
}
|
|||
|
|
public void PostMainThreadAction<T1, T2>(Action<T1, T2> action, T1 arg1, T2 arg2)
|
|||
|
|
{
|
|||
|
|
_mainThreadSynContext.Post(new SendOrPostCallback((o) =>
|
|||
|
|
{
|
|||
|
|
Action<T1, T2> e = (Action<T1, T2>)o.GetType().GetProperty("action").GetValue(o);
|
|||
|
|
T1 t1 = (T1)o.GetType().GetProperty("arg1").GetValue(o);
|
|||
|
|
T2 t2 = (T2)o.GetType().GetProperty("arg2").GetValue(o);
|
|||
|
|
if (e != null) e(t1, t2);
|
|||
|
|
}), new { action = action, arg1 = arg1, arg2 = arg2 });
|
|||
|
|
}
|
|||
|
|
public void PostMainThreadAction<T1, T2, T3>(Action<T1, T2, T3> action, T1 arg1, T2 arg2, T3 arg3)
|
|||
|
|
{
|
|||
|
|
_mainThreadSynContext.Post(new SendOrPostCallback((o) =>
|
|||
|
|
{
|
|||
|
|
Action<T1, T2, T3> e = (Action<T1, T2, T3>)o.GetType().GetProperty("action").GetValue(o);
|
|||
|
|
T1 t1 = (T1)o.GetType().GetProperty("arg1").GetValue(o);
|
|||
|
|
T2 t2 = (T2)o.GetType().GetProperty("arg2").GetValue(o);
|
|||
|
|
T3 t3 = (T3)o.GetType().GetProperty("arg3").GetValue(o);
|
|||
|
|
if (e != null) e(t1, t2, t3);
|
|||
|
|
}), new { action = action, arg1 = arg1, arg2 = arg2, arg3 = arg3 });
|
|||
|
|
}
|
|||
|
|
public void PostMainThreadAction<T1, T2, T3, T4>(Action<T1, T2, T3, T4> action, T1 arg1, T2 arg2, T3 arg3, T4 arg4)
|
|||
|
|
{
|
|||
|
|
_mainThreadSynContext.Post(new SendOrPostCallback((o) =>
|
|||
|
|
{
|
|||
|
|
Action<T1, T2, T3, T4> e = (Action<T1, T2, T3, T4>)o.GetType().GetProperty("action").GetValue(o);
|
|||
|
|
T1 t1 = (T1)o.GetType().GetProperty("arg1").GetValue(o);
|
|||
|
|
T2 t2 = (T2)o.GetType().GetProperty("arg2").GetValue(o);
|
|||
|
|
T3 t3 = (T3)o.GetType().GetProperty("arg3").GetValue(o);
|
|||
|
|
T4 t4 = (T4)o.GetType().GetProperty("arg4").GetValue(o);
|
|||
|
|
if (e != null) e(t1, t2, t3, t4);
|
|||
|
|
}), new { action = action, arg1 = arg1, arg2 = arg2, arg3 = arg3, arg4 = arg4 });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|