using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; namespace DownKyi.Core.Downloader { /// /// 文件合并改变事件 /// /// /// public delegate void FileMergeProgressChangedEventHandler(object sender, int e); /// /// 多线程下载器 /// public class MultiThreadDownloader { #region 属性 private string _url; private bool _rangeAllowed; private readonly HttpWebRequest _request; private Action _requestConfigure = req => req.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36"; #endregion #region 公共属性 /// /// RangeAllowed /// public bool RangeAllowed { get => _rangeAllowed; set => _rangeAllowed = value; } /// /// 临时文件夹 /// public string TempFileDirectory { get; set; } /// /// url地址 /// public string Url { get => _url; set => _url = value; } /// /// 第几部分 /// public int NumberOfParts { get; set; } /// /// 已接收字节数 /// public long TotalBytesReceived { get { try { return PartialDownloaderList.Where(t => t != null).Sum(t => t.TotalBytesRead); } catch (Exception e) { Logging.LogManager.Error(e); return 0; } } } /// /// 总进度 /// public float TotalProgress { get; private set; } /// /// 文件大小 /// public long Size { get; private set; } /// /// 下载速度 /// public float TotalSpeedInBytes => PartialDownloaderList.Sum(t => t.SpeedInBytes); /// /// 下载块 /// public List PartialDownloaderList { get; } /// /// 文件路径 /// public string FilePath { get; set; } #endregion #region 变量 /// /// 总下载进度更新事件 /// public event EventHandler TotalProgressChanged; /// /// 文件合并完成事件 /// public event EventHandler FileMergedComplete; /// /// 文件合并事件 /// public event FileMergeProgressChangedEventHandler FileMergeProgressChanged; private readonly AsyncOperation _aop; #endregion #region 下载管理器 /// /// 多线程下载管理器 /// /// /// /// /// public MultiThreadDownloader(string sourceUrl, string tempDir, string savePath, int numOfParts) { _url = sourceUrl; NumberOfParts = numOfParts; TempFileDirectory = tempDir; PartialDownloaderList = new List(); _aop = AsyncOperationManager.CreateOperation(null); FilePath = savePath; _request = WebRequest.Create(sourceUrl) as HttpWebRequest; } /// /// 多线程下载管理器 /// /// /// /// public MultiThreadDownloader(string sourceUrl, string savePath, int numOfParts) : this(sourceUrl, null, savePath, numOfParts) { TempFileDirectory = Environment.GetEnvironmentVariable("temp"); } /// /// 多线程下载管理器 /// /// /// public MultiThreadDownloader(string sourceUrl, int numOfParts) : this(sourceUrl, null, numOfParts) { } #endregion #region 事件 private void temp_DownloadPartCompleted(object sender, EventArgs e) { WaitOrResumeAll(PartialDownloaderList, true); if (TotalBytesReceived == Size) { UpdateProgress(); MergeParts(); return; } PartialDownloaderList.Sort((x, y) => y.RemainingBytes - x.RemainingBytes); int rem = PartialDownloaderList[0].RemainingBytes; if (rem < 50 * 1024) { WaitOrResumeAll(PartialDownloaderList, false); return; } int from = PartialDownloaderList[0].CurrentPosition + rem / 2; int to = PartialDownloaderList[0].To; if (from > to) { WaitOrResumeAll(PartialDownloaderList, false); return; } PartialDownloaderList[0].To = from - 1; WaitOrResumeAll(PartialDownloaderList, false); var temp = new PartialDownloader(_url, TempFileDirectory, Guid.NewGuid().ToString(), from, to, true); temp.DownloadPartCompleted += temp_DownloadPartCompleted; temp.DownloadPartProgressChanged += temp_DownloadPartProgressChanged; PartialDownloaderList.Add(temp); temp.Start(_requestConfigure); } void temp_DownloadPartProgressChanged(object sender, EventArgs e) { UpdateProgress(); } void UpdateProgress() { int pr = (int)(TotalBytesReceived * 1d / Size * 100); if (TotalProgress != pr) { TotalProgress = pr; if (TotalProgressChanged != null) { _aop.Post(state => TotalProgressChanged(this, EventArgs.Empty), null); } } } #endregion #region 方法 void CreateFirstPartitions() { Size = GetContentLength(ref _rangeAllowed, ref _url); int maximumPart = (int)(Size / (25 * 1024)); maximumPart = maximumPart == 0 ? 1 : maximumPart; if (!_rangeAllowed) { NumberOfParts = 1; } else if (NumberOfParts > maximumPart) { NumberOfParts = maximumPart; } for (int i = 0; i < NumberOfParts; i++) { var temp = CreateNew(i, NumberOfParts, Size); temp.DownloadPartProgressChanged += temp_DownloadPartProgressChanged; temp.DownloadPartCompleted += temp_DownloadPartCompleted; PartialDownloaderList.Add(temp); temp.Start(_requestConfigure); } } void MergeParts() { var mergeOrderedList = PartialDownloaderList.OrderBy(x => x.From); var dir = new FileInfo(FilePath).DirectoryName; Directory.CreateDirectory(dir); using (var fs = File.OpenWrite(FilePath)) { long totalBytesWrite = 0; int mergeProgress = 0; foreach (var item in mergeOrderedList) { using (var pdi = File.OpenRead(item.FullPath)) { byte[] buffer = new byte[4096]; int read; while ((read = pdi.Read(buffer, 0, buffer.Length)) > 0) { fs.Write(buffer, 0, read); totalBytesWrite += read; int temp = (int)(totalBytesWrite * 1d / Size * 100); if (temp != mergeProgress && FileMergeProgressChanged != null) { mergeProgress = temp; _aop.Post(state => FileMergeProgressChanged(this, temp), null); } } } try { File.Delete(item.FullPath); } catch { // ignored } } } if (FileMergedComplete != null) { _aop.Post(state => FileMergedComplete(state, EventArgs.Empty), this); } } PartialDownloader CreateNew(int order, int parts, long contentLength) { int division = (int)contentLength / parts; int remaining = (int)contentLength % parts; int start = division * order; int end = start + division - 1; end += order == parts - 1 ? remaining : 0; return new PartialDownloader(_url, TempFileDirectory, Guid.NewGuid().ToString("N"), start, end, true); } /// /// 暂停或继续 /// /// /// public static void WaitOrResumeAll(List list, bool wait) { foreach (var item in list) { if (wait) { item.Wait(); } else { item.ResumeAfterWait(); } } } /// /// 配置请求头 /// /// public void Configure(Action config) { _requestConfigure = config; } /// /// 获取内容长度 /// /// /// /// public long GetContentLength(ref bool rangeAllowed, ref string redirectedUrl) { _request.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36"; _request.ServicePoint.ConnectionLimit = 4; _requestConfigure(_request); long ctl; using (var resp = _request.GetResponse() as HttpWebResponse) { redirectedUrl = resp.ResponseUri.OriginalString; ctl = resp.ContentLength; rangeAllowed = resp.Headers.AllKeys.Select((v, i) => new { HeaderName = v, HeaderValue = resp.Headers[i] }).Any(k => k.HeaderName.ToLower().Contains("range") && k.HeaderValue.ToLower().Contains("byte")); _request.Abort(); } return ctl; } #endregion #region 公共方法 /// /// 暂停下载 /// public void Pause() { foreach (var t in PartialDownloaderList.Where(t => !t.Completed)) { t.Stop(); } Thread.Sleep(200); } /// /// 开始下载 /// public void Start() { Task th = new Task(CreateFirstPartitions); th.Start(); } /// /// 唤醒下载 /// public void Resume() { int count = PartialDownloaderList.Count; for (int i = 0; i < count; i++) { if (PartialDownloaderList[i].Stopped) { int from = PartialDownloaderList[i].CurrentPosition + 1; int to = PartialDownloaderList[i].To; if (from > to) { continue; } var temp = new PartialDownloader(_url, TempFileDirectory, Guid.NewGuid().ToString(), from, to, _rangeAllowed); temp.DownloadPartProgressChanged += temp_DownloadPartProgressChanged; temp.DownloadPartCompleted += temp_DownloadPartCompleted; PartialDownloaderList.Add(temp); PartialDownloaderList[i].To = PartialDownloaderList[i].CurrentPosition; temp.Start(_requestConfigure); } } } #endregion } }