ZeroLauncher/Manager/DownloadManager.cs

375 lines
15 KiB
C#
Raw Normal View History

2024-03-07 21:04:59 +08:00
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 });
}
}
}