using System; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Threading; namespace DownKyi.Core.Downloader { /// /// 部分下载器 /// public class PartialDownloader { /// /// 这部分完成事件 /// public event EventHandler DownloadPartCompleted; /// /// 部分下载进度改变事件 /// public event EventHandler DownloadPartProgressChanged; /// /// 部分下载停止事件 /// public event EventHandler DownloadPartStopped; private readonly AsyncOperation _aop = AsyncOperationManager.CreateOperation(null); readonly int[] _lastSpeeds; int _counter; private bool _wait; private int _to; private int _totalBytesRead; /// /// 下载已停止 /// public bool Stopped { get; private set; } /// /// 下载已完成 /// public bool Completed { get; private set; } /// /// 下载进度 /// public int Progress { get; private set; } /// /// 下载目录 /// public string Directory { get; } /// /// 文件名 /// public string FileName { get; } /// /// 已读字节数 /// public long TotalBytesRead => _totalBytesRead; /// /// 内容长度 /// public long ContentLength { get; private set; } /// /// RangeAllowed /// public bool RangeAllowed { get; } /// /// url /// public string Url { get; } /// /// to /// public int To { get => _to; set { _to = value; ContentLength = _to - From + 1; } } /// /// from /// public int From { get; } /// /// 当前位置 /// public int CurrentPosition => From + _totalBytesRead - 1; /// /// 剩余字节数 /// public int RemainingBytes => (int)(ContentLength - _totalBytesRead); /// /// 完整路径 /// public string FullPath => Path.Combine(Directory, FileName); /// /// 下载速度 /// public int SpeedInBytes { get { if (Completed) { return 0; } int totalSpeeds = _lastSpeeds.Sum(); return totalSpeeds / 10; } } /// /// 部分块下载 /// /// /// /// /// /// /// public PartialDownloader(string url, string dir, string fileGuid, int from, int to, bool rangeAllowed) { From = from; _to = to; Url = url; RangeAllowed = rangeAllowed; FileName = fileGuid; Directory = dir; _lastSpeeds = new int[10]; } void DownloadProcedure(Action config) { using (var file = new FileStream(FullPath, FileMode.Create, FileAccess.ReadWrite, FileShare.Delete)) { var sw = new Stopwatch(); if (WebRequest.Create(Url) is HttpWebRequest 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"; req.AllowAutoRedirect = true; req.MaximumAutomaticRedirections = 5; req.ServicePoint.ConnectionLimit += 1; req.ServicePoint.Expect100Continue = true; req.ProtocolVersion = HttpVersion.Version11; config(req); if (RangeAllowed) { req.AddRange(From, _to); } if (req.GetResponse() is HttpWebResponse resp) { ContentLength = resp.ContentLength; if (ContentLength <= 0 || (RangeAllowed && ContentLength != _to - From + 1)) { throw new Exception("Invalid response content"); } using (var tempStream = resp.GetResponseStream()) { int bytesRead; byte[] buffer = new byte[4096]; sw.Start(); while ((bytesRead = tempStream.Read(buffer, 0, buffer.Length)) > 0) { if (_totalBytesRead + bytesRead > ContentLength) { bytesRead = (int)(ContentLength - _totalBytesRead); } file.Write(buffer, 0, bytesRead); _totalBytesRead += bytesRead; _lastSpeeds[_counter] = (int)(_totalBytesRead / Math.Ceiling(sw.Elapsed.TotalSeconds)); _counter = (_counter >= 9) ? 0 : _counter + 1; int tempProgress = (int)(_totalBytesRead * 100 / ContentLength); if (Progress != tempProgress) { Progress = tempProgress; _aop.Post(state => { DownloadPartProgressChanged?.Invoke(this, EventArgs.Empty); }, null); } if (Stopped || (RangeAllowed && _totalBytesRead == ContentLength)) { break; } } } } req.Abort(); } sw.Stop(); if (!Stopped && DownloadPartCompleted != null) { _aop.Post(state => { Completed = true; DownloadPartCompleted(this, EventArgs.Empty); }, null); } if (Stopped && DownloadPartStopped != null) { _aop.Post(state => DownloadPartStopped(this, EventArgs.Empty), null); } } } /// /// 启动下载 /// public void Start(Action config) { Stopped = false; var procThread = new Thread(_ => DownloadProcedure(config)); procThread.Start(); } /// /// 下载停止 /// public void Stop() { Stopped = true; } /// /// 暂停等待下载 /// public void Wait() { _wait = true; } /// /// 稍后唤醒 /// public void ResumeAfterWait() { _wait = false; } } }