ZeroLauncher/Manager/DownloadManager.cs
2024-03-07 21:04:59 +08:00

375 lines
15 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 });
}
}
}