浏览代码

新增多线程下载器

懒得勤快 7 年之前
父节点
当前提交
7ba46c8d6e

+ 1 - 1
Masuit.Tools.Core/Masuit.Tools.Core.csproj

@@ -2,7 +2,7 @@
 
   <PropertyGroup>
     <TargetFramework>netcoreapp2.0</TargetFramework>
-    <Version>1.9.4.1</Version>
+    <Version>1.9.5</Version>
     <Authors>懒得勤快</Authors>
     <Company>masuit.com</Company>
     <Description>包含一些常用的操作类,大都是静态类,加密解密,反射操作,硬件信息,字符串扩展方法,日期时间扩展操作,大文件拷贝,图像裁剪,html处理,验证码、NoSql等常用封装。

+ 289 - 0
Masuit.Tools.Core/Net/MultiThreadDownloader.cs

@@ -0,0 +1,289 @@
+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 Masuit.Tools.Core.Net
+{
+    public delegate void FileMergeProgressChangedEventHandler(object sender, int e);
+    public class MultiThreadDownloader
+    {
+        #region Variables
+        public event EventHandler TotalProgressChanged;
+        public event FileMergeProgressChangedEventHandler FileMergeProgressChanged;
+        private readonly AsyncOperation _aop;
+        #endregion
+
+        #region DownloadManager
+        public MultiThreadDownloader(string sourceUrl, string tempDir, string savePath, int numOfParts)
+        {
+            _url = sourceUrl;
+            NumberOfParts = numOfParts;
+            TempFileDirectory = tempDir;
+            PartialDownloaderList = new List<PartialDownloader>();
+            _aop = AsyncOperationManager.CreateOperation(null);
+            FilePath = savePath;
+        }
+        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 Events
+
+        private void temp_DownloadPartCompleted(object sender, EventArgs e)
+        {
+            WaitOrResumeAll(PartialDownloaderList, true);
+
+            if (TotalBytesReceived == Size)
+            {
+                UpdateProgress();
+                MergeParts();
+                return;
+            }
+
+            OrderByRemaining(PartialDownloaderList);
+
+            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);
+
+            PartialDownloader temp = new PartialDownloader(_url, TempFileDirectory, Guid.NewGuid().ToString(), from, to, true);
+            temp.DownloadPartCompleted += temp_DownloadPartCompleted;
+            temp.DownloadPartProgressChanged += temp_DownloadPartProgressChanged;
+            PartialDownloaderList.Add(temp);
+            temp.Start();
+        }
+
+        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 Helpers
+        void CreateFirstPartitions()
+        {
+            Size = GetContentLength(_url, 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++)
+            {
+                PartialDownloader temp = CreateNewPd(i, NumberOfParts, Size);
+                temp.DownloadPartProgressChanged += temp_DownloadPartProgressChanged;
+                temp.DownloadPartCompleted += temp_DownloadPartCompleted;
+                PartialDownloaderList.Add(temp);
+                temp.Start();
+            }
+        }
+        void MergeParts()
+        {
+            List<PartialDownloader> mergeOrderedList = SortPDsByFrom(PartialDownloaderList);
+            using (var fs = new FileStream(FilePath, FileMode.Create, FileAccess.ReadWrite))
+            {
+                long totalBytesWritten = 0;
+                int mergeProgress = 0;
+                foreach (var item in mergeOrderedList)
+                {
+                    using (FileStream pds = new FileStream(item.FullPath, FileMode.Open, FileAccess.Read))
+                    {
+                        byte[] buffer = new byte[4096];
+                        int read;
+                        while ((read = pds.Read(buffer, 0, buffer.Length)) > 0)
+                        {
+                            fs.Write(buffer, 0, read);
+                            totalBytesWritten += read;
+                            int temp = (int)(totalBytesWritten * 1d / Size * 100);
+                            if (temp != mergeProgress && FileMergeProgressChanged != null)
+                            {
+                                mergeProgress = temp;
+                                _aop.Post(state => FileMergeProgressChanged(this, temp), null);
+                            }
+                        }
+                    }
+                    File.Delete(item.FullPath);
+                }
+            }
+        }
+        PartialDownloader CreateNewPd(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(), start, end, true);
+        }
+        public static void WaitOrResumeAll(List<PartialDownloader> list, bool wait)
+        {
+            foreach (PartialDownloader item in list)
+            {
+                if (wait)
+                    item.Wait();
+                else
+                    item.ResumeAfterWait();
+            }
+        }
+
+
+        private static void BubbleSort(List<PartialDownloader> list)
+        {
+            bool switched = true;
+            while (switched)
+            {
+                switched = false;
+                for (int i = 0; i < list.Count - 1; i++)
+                {
+                    if (list[i].RemainingBytes < list[i + 1].RemainingBytes)
+                    {
+                        PartialDownloader temp = list[i];
+                        list[i] = list[i + 1];
+                        list[i + 1] = temp;
+                        switched = true;
+                    }
+                }
+            }
+        }
+        //Sorts the downloader by From property to merge the parts
+        public static List<PartialDownloader> SortPDsByFrom(List<PartialDownloader> list)
+        {
+            return list.OrderBy(x => x.From).ToList();
+        }
+        public static void OrderByRemaining(List<PartialDownloader> list)
+        {
+            BubbleSort(list);
+        }
+
+        public static long GetContentLength(string url, ref bool rangeAllowed, ref string redirectedUrl)
+        {
+            HttpWebRequest req = WebRequest.Create(url) as HttpWebRequest;
+            req.UserAgent = "Mozilla/4.0 (compatible; MSIE 11.0; Windows NT 6.2; .NET CLR 1.0.3705;)";
+            req.ServicePoint.ConnectionLimit = 4;
+            HttpWebResponse resp = req.GetResponse() as HttpWebResponse;
+            redirectedUrl = resp.ResponseUri.OriginalString;
+            long 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"));
+            resp.Close();
+            req.Abort();
+            return ctl;
+        }
+        #endregion
+
+        #region Public Methods
+        public void Pause()
+        {
+            foreach (var t in PartialDownloaderList)
+            {
+                if (!t.Completed)
+                    t.Stop();
+            }
+
+            //Setting a Thread.Sleep ensures all downloads are stopped and exit from loop.
+            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;
+                    PartialDownloader 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();
+                }
+            }
+        }
+        #endregion
+
+        #region Property Variables
+
+        private string _url;
+        private bool _rangeAllowed;
+
+        #endregion
+
+        #region Properties
+        public bool RangeAllowed
+        {
+            get => _rangeAllowed;
+            set => _rangeAllowed = value;
+        }
+        public string TempFileDirectory { get; set; }
+
+        public string Url
+        {
+            get => _url;
+            set => _url = value;
+        }
+
+        public int NumberOfParts { get; set; }
+
+        public long TotalBytesReceived => PartialDownloaderList.Where(t => t != null).Sum(t => t.TotalBytesRead);
+
+        public int TotalProgress { get; private set; }
+
+        public long Size { get; private set; }
+
+        public int TotalSpeedInBytes => PartialDownloaderList.Sum(t => t.SpeedInBytes);
+        public List<PartialDownloader> PartialDownloaderList { get; }
+
+        public string FilePath { get; set; }
+
+        #endregion
+
+    }
+}

+ 220 - 0
Masuit.Tools.Core/Net/PartialDownloader.cs

@@ -0,0 +1,220 @@
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Threading;
+
+namespace Masuit.Tools.Core.Net
+{
+    public class PartialDownloader
+    {
+        #region Variables
+        public event EventHandler DownloadPartCompleted;
+        public event EventHandler DownloadPartProgressChanged;
+        public event EventHandler DownloadPartStopped;
+        HttpWebRequest _req;
+        HttpWebResponse _resp;
+        Stream _tempStream;
+        FileStream _file;
+        private readonly AsyncOperation _aop = AsyncOperationManager.CreateOperation(null);
+        private readonly Stopwatch _stp;
+        readonly int[] _lastSpeeds;
+        int _counter;
+        bool _stop, _wait;
+        #endregion
+
+        #region PartialDownloader
+        public PartialDownloader(string url, string dir, string fileGuid, int from, int to, bool rangeAllowed)
+        {
+            _from = from;
+            _to = to;
+            _url = url;
+            _rangeAllowed = rangeAllowed;
+            _fileGuid = fileGuid;
+            _directory = dir;
+            _lastSpeeds = new int[10];
+            _stp = new Stopwatch();
+        }
+        #endregion
+
+        void DownloadProcedure()
+        {
+            _file = new FileStream(FullPath, FileMode.Create, FileAccess.ReadWrite);
+            #region Request-Response
+            _req = WebRequest.Create(_url) as HttpWebRequest;
+            if (_req != null)
+            {
+                _req.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)";
+                _req.AllowAutoRedirect = true;
+                _req.MaximumAutomaticRedirections = 5;
+                _req.ServicePoint.ConnectionLimit += 1;
+                _req.ServicePoint.Expect100Continue = true;
+                _req.ProtocolVersion = HttpVersion.Version10;
+                ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Ssl3;
+                ServicePointManager.Expect100Continue = true;
+                if (_rangeAllowed)
+                    _req.AddRange(_from, _to);
+                _resp = _req.GetResponse() as HttpWebResponse;
+
+                #endregion
+
+                #region Some Stuff
+
+                if (_resp != null)
+                {
+                    _contentLength = _resp.ContentLength;
+                    if (_contentLength <= 0 || (_rangeAllowed && _contentLength != _to - _from + 1))
+                        throw new Exception("Invalid response content");
+                    _tempStream = _resp.GetResponseStream();
+                    int bytesRead;
+                    byte[] buffer = new byte[4096];
+                    _stp.Start();
+
+                    #endregion
+
+                    #region Procedure Loop
+
+                    while (_tempStream != null && (bytesRead = _tempStream.Read(buffer, 0, buffer.Length)) > 0)
+                    {
+                        while (_wait)
+                        {
+                        }
+
+                        if (_totalBytesRead + bytesRead > _contentLength)
+                            bytesRead = (int)(_contentLength - _totalBytesRead);
+                        _file.Write(buffer, 0, bytesRead);
+                        _totalBytesRead += bytesRead;
+                        _lastSpeeds[_counter] = (int)(_totalBytesRead / Math.Ceiling(_stp.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 (_stop || (_rangeAllowed && _totalBytesRead == _contentLength))
+                        {
+                            break;
+                        }
+                    }
+
+                    #endregion
+
+                    #region Close Resources
+
+                    _file.Close();
+                    _resp.Close();
+                }
+
+                _tempStream?.Close();
+                _req.Abort();
+            }
+
+            _stp.Stop();
+            #endregion
+
+            #region Fire Events
+            if (!_stop && DownloadPartCompleted != null)
+                _aop.Post(state =>
+                {
+                    _completed = true;
+                    DownloadPartCompleted(this, EventArgs.Empty);
+                }, null);
+
+            if (_stop && DownloadPartStopped != null)
+                _aop.Post(state => DownloadPartStopped(this, EventArgs.Empty), null);
+
+            #endregion
+        }
+
+        #region Public Methods
+        public void Start()
+        {
+            _stop = false;
+            Thread procThread = new Thread(DownloadProcedure);
+            procThread.Start();
+        }
+
+        public void Stop()
+        {
+            _stop = true;
+        }
+        //Wait is used when repartitiate a partition securely in this project
+        public void Wait()
+        {
+            _wait = true;
+        }
+        public void ResumeAfterWait()
+        {
+            _wait = false;
+        }
+        #endregion
+
+        #region Property Variables
+        private readonly int _from;
+        private int _to;
+        private readonly string _url;
+        private readonly bool _rangeAllowed;
+        private long _contentLength;
+        private int _totalBytesRead;
+        private readonly string _fileGuid;
+        private readonly string _directory;
+        private int _progress;
+        private bool _completed;
+        #endregion
+
+        #region Properties
+        public bool Stopped => _stop;
+
+        public bool Completed => _completed;
+
+        public int Progress => _progress;
+
+        public string Directory => _directory;
+
+        public string FileName => _fileGuid;
+
+        public long TotalBytesRead => _totalBytesRead;
+
+        public long ContentLength => _contentLength;
+
+        public bool RangeAllowed => _rangeAllowed;
+
+        public string Url => _url;
+
+        public int To
+        {
+            get => _to;
+            set
+            {
+                _to = value;
+                _contentLength = _to - _from + 1;
+            }
+        }
+
+        public int From => _from;
+
+        public int CurrentPosition => _from + _totalBytesRead - 1;
+
+        public int RemainingBytes => (int)(_contentLength - _totalBytesRead);
+
+        public string FullPath => Path.Combine(_directory, _fileGuid);
+
+        public int SpeedInBytes
+        {
+            get
+            {
+                if (_completed)
+                    return 0;
+
+                int totalSpeeds = _lastSpeeds.Sum();
+
+                return totalSpeeds / 10;
+            }
+        }
+        #endregion
+    }
+}

+ 3 - 1
Masuit.Tools/Masuit.Tools.csproj

@@ -42,7 +42,7 @@
       <HintPath>..\..\MyBlogs\packages\AngleSharp.0.9.9.2\lib\net45\AngleSharp.dll</HintPath>
     </Reference>
     <Reference Include="HtmlSanitizer, Version=3.0.0.0, Culture=neutral, PublicKeyToken=61c49a1a9e79cc28, processorArchitecture=MSIL">
-      <HintPath>..\packages\HtmlSanitizer.4.0.183\lib\net45\HtmlSanitizer.dll</HintPath>
+      <HintPath>..\..\MyBlogs\packages\HtmlSanitizer.4.0.185\lib\net45\HtmlSanitizer.dll</HintPath>
     </Reference>
     <Reference Include="ICSharpCode.SharpZipLib, Version=0.86.0.518, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL">
       <HintPath>..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll</HintPath>
@@ -100,6 +100,8 @@
     <Compile Include="Net\CacheHelper.cs" />
     <Compile Include="Net\CookieHelper.cs" />
     <Compile Include="Net\FtpClient.cs" />
+    <Compile Include="Net\MultiThreadDownloader.cs" />
+    <Compile Include="Net\PartialDownloader.cs" />
     <Compile Include="Net\SocketClient.cs" />
     <Compile Include="NoSQL\RedisConnectionManager.cs" />
     <Compile Include="NoSQL\RedisHelper.cs" />

+ 289 - 0
Masuit.Tools/Net/MultiThreadDownloader.cs

@@ -0,0 +1,289 @@
+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 Masuit.Tools.Net
+{
+    public delegate void FileMergeProgressChangedEventHandler(object sender, int e);
+    public class MultiThreadDownloader
+    {
+        #region Variables
+        public event EventHandler TotalProgressChanged;
+        public event FileMergeProgressChangedEventHandler FileMergeProgressChanged;
+        private readonly AsyncOperation _aop;
+        #endregion
+
+        #region DownloadManager
+        public MultiThreadDownloader(string sourceUrl, string tempDir, string savePath, int numOfParts)
+        {
+            _url = sourceUrl;
+            NumberOfParts = numOfParts;
+            TempFileDirectory = tempDir;
+            PartialDownloaderList = new List<PartialDownloader>();
+            _aop = AsyncOperationManager.CreateOperation(null);
+            FilePath = savePath;
+        }
+        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 Events
+
+        private void temp_DownloadPartCompleted(object sender, EventArgs e)
+        {
+            WaitOrResumeAll(PartialDownloaderList, true);
+
+            if (TotalBytesReceived == Size)
+            {
+                UpdateProgress();
+                MergeParts();
+                return;
+            }
+
+            OrderByRemaining(PartialDownloaderList);
+
+            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);
+
+            PartialDownloader temp = new PartialDownloader(_url, TempFileDirectory, Guid.NewGuid().ToString(), from, to, true);
+            temp.DownloadPartCompleted += temp_DownloadPartCompleted;
+            temp.DownloadPartProgressChanged += temp_DownloadPartProgressChanged;
+            PartialDownloaderList.Add(temp);
+            temp.Start();
+        }
+
+        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 Helpers
+        void CreateFirstPartitions()
+        {
+            Size = GetContentLength(_url, 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++)
+            {
+                PartialDownloader temp = CreateNewPd(i, NumberOfParts, Size);
+                temp.DownloadPartProgressChanged += temp_DownloadPartProgressChanged;
+                temp.DownloadPartCompleted += temp_DownloadPartCompleted;
+                PartialDownloaderList.Add(temp);
+                temp.Start();
+            }
+        }
+        void MergeParts()
+        {
+            List<PartialDownloader> mergeOrderedList = SortPDsByFrom(PartialDownloaderList);
+            using (var fs = new FileStream(FilePath, FileMode.Create, FileAccess.ReadWrite))
+            {
+                long totalBytesWritten = 0;
+                int mergeProgress = 0;
+                foreach (var item in mergeOrderedList)
+                {
+                    using (FileStream pds = new FileStream(item.FullPath, FileMode.Open, FileAccess.Read))
+                    {
+                        byte[] buffer = new byte[4096];
+                        int read;
+                        while ((read = pds.Read(buffer, 0, buffer.Length)) > 0)
+                        {
+                            fs.Write(buffer, 0, read);
+                            totalBytesWritten += read;
+                            int temp = (int)(totalBytesWritten * 1d / Size * 100);
+                            if (temp != mergeProgress && FileMergeProgressChanged != null)
+                            {
+                                mergeProgress = temp;
+                                _aop.Post(state => FileMergeProgressChanged(this, temp), null);
+                            }
+                        }
+                    }
+                    File.Delete(item.FullPath);
+                }
+            }
+        }
+        PartialDownloader CreateNewPd(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(), start, end, true);
+        }
+        public static void WaitOrResumeAll(List<PartialDownloader> list, bool wait)
+        {
+            foreach (PartialDownloader item in list)
+            {
+                if (wait)
+                    item.Wait();
+                else
+                    item.ResumeAfterWait();
+            }
+        }
+
+
+        private static void BubbleSort(List<PartialDownloader> list)
+        {
+            bool switched = true;
+            while (switched)
+            {
+                switched = false;
+                for (int i = 0; i < list.Count - 1; i++)
+                {
+                    if (list[i].RemainingBytes < list[i + 1].RemainingBytes)
+                    {
+                        PartialDownloader temp = list[i];
+                        list[i] = list[i + 1];
+                        list[i + 1] = temp;
+                        switched = true;
+                    }
+                }
+            }
+        }
+        //Sorts the downloader by From property to merge the parts
+        public static List<PartialDownloader> SortPDsByFrom(List<PartialDownloader> list)
+        {
+            return list.OrderBy(x => x.From).ToList();
+        }
+        public static void OrderByRemaining(List<PartialDownloader> list)
+        {
+            BubbleSort(list);
+        }
+
+        public static long GetContentLength(string url, ref bool rangeAllowed, ref string redirectedUrl)
+        {
+            HttpWebRequest req = WebRequest.Create(url) as HttpWebRequest;
+            req.UserAgent = "Mozilla/4.0 (compatible; MSIE 11.0; Windows NT 6.2; .NET CLR 1.0.3705;)";
+            req.ServicePoint.ConnectionLimit = 4;
+            HttpWebResponse resp = req.GetResponse() as HttpWebResponse;
+            redirectedUrl = resp.ResponseUri.OriginalString;
+            long 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"));
+            resp.Close();
+            req.Abort();
+            return ctl;
+        }
+        #endregion
+
+        #region Public Methods
+        public void Pause()
+        {
+            foreach (var t in PartialDownloaderList)
+            {
+                if (!t.Completed)
+                    t.Stop();
+            }
+
+            //Setting a Thread.Sleep ensures all downloads are stopped and exit from loop.
+            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;
+                    PartialDownloader 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();
+                }
+            }
+        }
+        #endregion
+
+        #region Property Variables
+
+        private string _url;
+        private bool _rangeAllowed;
+
+        #endregion
+
+        #region Properties
+        public bool RangeAllowed
+        {
+            get => _rangeAllowed;
+            set => _rangeAllowed = value;
+        }
+        public string TempFileDirectory { get; set; }
+
+        public string Url
+        {
+            get => _url;
+            set => _url = value;
+        }
+
+        public int NumberOfParts { get; set; }
+
+        public long TotalBytesReceived => PartialDownloaderList.Where(t => t != null).Sum(t => t.TotalBytesRead);
+
+        public int TotalProgress { get; private set; }
+
+        public long Size { get; private set; }
+
+        public int TotalSpeedInBytes => PartialDownloaderList.Sum(t => t.SpeedInBytes);
+        public List<PartialDownloader> PartialDownloaderList { get; }
+
+        public string FilePath { get; set; }
+
+        #endregion
+
+    }
+}

+ 220 - 0
Masuit.Tools/Net/PartialDownloader.cs

@@ -0,0 +1,220 @@
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Threading;
+
+namespace Masuit.Tools.Net
+{
+    public class PartialDownloader
+    {
+        #region Variables
+        public event EventHandler DownloadPartCompleted;
+        public event EventHandler DownloadPartProgressChanged;
+        public event EventHandler DownloadPartStopped;
+        HttpWebRequest _req;
+        HttpWebResponse _resp;
+        Stream _tempStream;
+        FileStream _file;
+        private readonly AsyncOperation _aop = AsyncOperationManager.CreateOperation(null);
+        private readonly Stopwatch _stp;
+        readonly int[] _lastSpeeds;
+        int _counter;
+        bool _stop, _wait;
+        #endregion
+
+        #region PartialDownloader
+        public PartialDownloader(string url, string dir, string fileGuid, int from, int to, bool rangeAllowed)
+        {
+            _from = from;
+            _to = to;
+            _url = url;
+            _rangeAllowed = rangeAllowed;
+            _fileGuid = fileGuid;
+            _directory = dir;
+            _lastSpeeds = new int[10];
+            _stp = new Stopwatch();
+        }
+        #endregion
+
+        void DownloadProcedure()
+        {
+            _file = new FileStream(FullPath, FileMode.Create, FileAccess.ReadWrite);
+            #region Request-Response
+            _req = WebRequest.Create(_url) as HttpWebRequest;
+            if (_req != null)
+            {
+                _req.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)";
+                _req.AllowAutoRedirect = true;
+                _req.MaximumAutomaticRedirections = 5;
+                _req.ServicePoint.ConnectionLimit += 1;
+                _req.ServicePoint.Expect100Continue = true;
+                _req.ProtocolVersion = HttpVersion.Version10;
+                ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Ssl3;
+                ServicePointManager.Expect100Continue = true;
+                if (_rangeAllowed)
+                    _req.AddRange(_from, _to);
+                _resp = _req.GetResponse() as HttpWebResponse;
+
+                #endregion
+
+                #region Some Stuff
+
+                if (_resp != null)
+                {
+                    _contentLength = _resp.ContentLength;
+                    if (_contentLength <= 0 || (_rangeAllowed && _contentLength != _to - _from + 1))
+                        throw new Exception("Invalid response content");
+                    _tempStream = _resp.GetResponseStream();
+                    int bytesRead;
+                    byte[] buffer = new byte[4096];
+                    _stp.Start();
+
+                    #endregion
+
+                    #region Procedure Loop
+
+                    while (_tempStream != null && (bytesRead = _tempStream.Read(buffer, 0, buffer.Length)) > 0)
+                    {
+                        while (_wait)
+                        {
+                        }
+
+                        if (_totalBytesRead + bytesRead > _contentLength)
+                            bytesRead = (int)(_contentLength - _totalBytesRead);
+                        _file.Write(buffer, 0, bytesRead);
+                        _totalBytesRead += bytesRead;
+                        _lastSpeeds[_counter] = (int)(_totalBytesRead / Math.Ceiling(_stp.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 (_stop || (_rangeAllowed && _totalBytesRead == _contentLength))
+                        {
+                            break;
+                        }
+                    }
+
+                    #endregion
+
+                    #region Close Resources
+
+                    _file.Close();
+                    _resp.Close();
+                }
+
+                _tempStream?.Close();
+                _req.Abort();
+            }
+
+            _stp.Stop();
+            #endregion
+
+            #region Fire Events
+            if (!_stop && DownloadPartCompleted != null)
+                _aop.Post(state =>
+                {
+                    _completed = true;
+                    DownloadPartCompleted(this, EventArgs.Empty);
+                }, null);
+
+            if (_stop && DownloadPartStopped != null)
+                _aop.Post(state => DownloadPartStopped(this, EventArgs.Empty), null);
+
+            #endregion
+        }
+
+        #region Public Methods
+        public void Start()
+        {
+            _stop = false;
+            Thread procThread = new Thread(DownloadProcedure);
+            procThread.Start();
+        }
+
+        public void Stop()
+        {
+            _stop = true;
+        }
+        //Wait is used when repartitiate a partition securely in this project
+        public void Wait()
+        {
+            _wait = true;
+        }
+        public void ResumeAfterWait()
+        {
+            _wait = false;
+        }
+        #endregion
+
+        #region Property Variables
+        private readonly int _from;
+        private int _to;
+        private readonly string _url;
+        private readonly bool _rangeAllowed;
+        private long _contentLength;
+        private int _totalBytesRead;
+        private readonly string _fileGuid;
+        private readonly string _directory;
+        private int _progress;
+        private bool _completed;
+        #endregion
+
+        #region Properties
+        public bool Stopped => _stop;
+
+        public bool Completed => _completed;
+
+        public int Progress => _progress;
+
+        public string Directory => _directory;
+
+        public string FileName => _fileGuid;
+
+        public long TotalBytesRead => _totalBytesRead;
+
+        public long ContentLength => _contentLength;
+
+        public bool RangeAllowed => _rangeAllowed;
+
+        public string Url => _url;
+
+        public int To
+        {
+            get => _to;
+            set
+            {
+                _to = value;
+                _contentLength = _to - _from + 1;
+            }
+        }
+
+        public int From => _from;
+
+        public int CurrentPosition => _from + _totalBytesRead - 1;
+
+        public int RemainingBytes => (int)(_contentLength - _totalBytesRead);
+
+        public string FullPath => Path.Combine(_directory, _fileGuid);
+
+        public int SpeedInBytes
+        {
+            get
+            {
+                if (_completed)
+                    return 0;
+
+                int totalSpeeds = _lastSpeeds.Sum();
+
+                return totalSpeeds / 10;
+            }
+        }
+        #endregion
+    }
+}

+ 42 - 72
Masuit.Tools/Net/WebExtension.cs

@@ -1,11 +1,11 @@
 using System;
 using System.Collections.Generic;
 using System.Configuration;
+using System.Linq;
 using System.Net.Http;
 using System.Runtime.Remoting.Messaging;
 using System.Web;
 using System.Web.SessionState;
-using Masuit.Tools.Logging;
 using Masuit.Tools.Models;
 using Masuit.Tools.NoSQL;
 using Masuit.Tools.Security;
@@ -292,47 +292,12 @@ namespace Masuit.Tools.Net
             ip.MatchInetAddress(out var isIpAddress);
             if (isIpAddress)
             {
-                string ak = ConfigurationManager.AppSettings["BaiduAK"];
-                if (string.IsNullOrEmpty(ak))
-                {
-                    throw new Exception("未配置BaiduAK,请先在您的应用程序web.config或者App.config中的AppSettings节点下添加BaiduAK配置节(注意大小写)");
-                }
-                using (HttpClient client = new HttpClient() { BaseAddress = new Uri("http://api.map.baidu.com") })
+                var address = GetPhysicsAddressInfo(ip);
+                if (address.Status == 0)
                 {
-                    try
-                    {
-                        string ipJson = client.GetStringAsync($"/location/ip?ak={ak}&ip={ip}&coor=bd09ll").Result;
-                        var ipAddress = JsonConvert.DeserializeObject<BaiduIP>(ipJson);
-                        if (ipAddress.Status == 0)
-                        {
-                            LatiLongitude point = ipAddress.AddressInfo.LatiLongitude;
-                            string result = client.GetStringAsync($"/geocoder/v2/?location={point.Y},{point.X}&output=json&pois=1&radius=1000&latest_admin=1&coordtype=bd09ll&ak={ak}").Result;
-                            PhysicsAddress address = JsonConvert.DeserializeObject<PhysicsAddress>(result);
-                            if (address.Status == 0)
-                            {
-                                string detail = $"{address.AddressResult.FormattedAddress} {address.AddressResult.AddressComponent.Direction}{address.AddressResult.AddressComponent.Distance ?? "0"}米";
-                                List<string> pois = new List<string>();
-                                address.AddressResult.Pois.ForEach(p => { pois.Add($"{p.AddressDetail}{p.Name} {p.Direction}{p.Distance ?? "0"}米"); });
-                                return new Tuple<string, List<string>>(detail, pois);
-                            }
-                        }
-                        else
-                        {
-                            using (var client2 = new HttpClient { BaseAddress = new Uri("http://ip.taobao.com") })
-                            {
-                                var result = client2.GetStringAsync($"/service/getIpInfo.php?ip={ip}").Result;
-                                TaobaoIP taobaoIp = JsonConvert.DeserializeObject<TaobaoIP>(result);
-                                if (taobaoIp.Code == 0)
-                                {
-                                    return new Tuple<string, List<string>>(taobaoIp.IpData.Country + taobaoIp.IpData.Region + taobaoIp.IpData.City, new List<string>());
-                                }
-                            }
-                        }
-                    }
-                    catch (Exception e)
-                    {
-                        LogManager.Error(e);
-                    }
+                    string detail = $"{address.AddressResult.FormattedAddress} {address.AddressResult.AddressComponent.Direction}{address.AddressResult.AddressComponent.Distance ?? "0"}米";
+                    List<string> pois = address.AddressResult.Pois.Select(p => $"{p.AddressDetail}{p.Name} {p.Direction}{p.Distance ?? "0"}米").ToList();
+                    return new Tuple<string, List<string>>(detail, pois);
                 }
                 return new Tuple<string, List<string>>("IP地址不正确", new List<string>());
             }
@@ -357,37 +322,45 @@ namespace Masuit.Tools.Net
                 using (HttpClient client = new HttpClient() { BaseAddress = new Uri("http://api.map.baidu.com") })
                 {
                     client.DefaultRequestHeaders.Referrer = new Uri("http://lbsyun.baidu.com/jsdemo.htm");
-                    try
+                    var task = client.GetAsync($"/location/ip?ak={ak}&ip={ip}&coor=bd09ll").ContinueWith(async t =>
                     {
-                        string ipJson = client.GetStringAsync($"/location/ip?ak={ak}&ip={ip}&coor=bd09ll").Result;
-                        var ipAddress = JsonConvert.DeserializeObject<BaiduIP>(ipJson);
-                        if (ipAddress.Status == 0)
+                        var res = await t;
+                        if (res.IsSuccessStatusCode)
                         {
-                            LatiLongitude point = ipAddress.AddressInfo.LatiLongitude;
-                            string result = client.GetStringAsync($"/geocoder/v2/?location={point.Y},{point.X}&output=json&pois=1&radius=1000&latest_admin=1&coordtype=bd09ll&ak={ak}").Result;
-                            PhysicsAddress address = JsonConvert.DeserializeObject<PhysicsAddress>(result);
-                            if (address.Status == 0)
+                            var ipAddress = JsonConvert.DeserializeObject<BaiduIP>(await res.Content.ReadAsStringAsync());
+                            if (ipAddress.Status == 0)
                             {
-                                return address;
+                                LatiLongitude point = ipAddress.AddressInfo.LatiLongitude;
+                                string result = client.GetStringAsync($"/geocoder/v2/?location={point.Y},{point.X}&output=json&pois=1&radius=1000&latest_admin=1&coordtype=bd09ll&ak={ak}").Result;
+                                PhysicsAddress address = JsonConvert.DeserializeObject<PhysicsAddress>(result);
+                                if (address.Status == 0)
+                                {
+                                    return address;
+                                }
                             }
-                        }
-                        else
-                        {
-                            using (var client2 = new HttpClient { BaseAddress = new Uri("http://ip.taobao.com") })
+                            else
                             {
-                                var result = client2.GetStringAsync($"/service/getIpInfo.php?ip={ip}").Result;
-                                TaobaoIP taobaoIp = JsonConvert.DeserializeObject<TaobaoIP>(result);
-                                if (taobaoIp.Code == 0)
+                                using (var client2 = new HttpClient { BaseAddress = new Uri("http://ip.taobao.com") })
                                 {
-                                    return new PhysicsAddress() { Status = 0, AddressResult = new AddressResult() { FormattedAddress = taobaoIp.IpData.Country + taobaoIp.IpData.Region + taobaoIp.IpData.City, AddressComponent = new AddressComponent() { Province = taobaoIp.IpData.Region }, Pois = new List<Pois>() } };
+                                    return await await client2.GetAsync($"/service/getIpInfo.php?ip={ip}").ContinueWith(async tt =>
+                                    {
+                                        var result = await tt;
+                                        if (result.IsSuccessStatusCode)
+                                        {
+                                            TaobaoIP taobaoIp = JsonConvert.DeserializeObject<TaobaoIP>(await result.Content.ReadAsStringAsync());
+                                            if (taobaoIp.Code == 0)
+                                            {
+                                                return new PhysicsAddress() { Status = 0, AddressResult = new AddressResult() { FormattedAddress = taobaoIp.IpData.Country + taobaoIp.IpData.Region + taobaoIp.IpData.City, AddressComponent = new AddressComponent() { Province = taobaoIp.IpData.Region }, Pois = new List<Pois>() } };
+                                            }
+                                        }
+                                        return null;
+                                    });
                                 }
                             }
                         }
-                    }
-                    catch (Exception e)
-                    {
-                        LogManager.Error(e);
-                    }
+                        return null;
+                    });
+                    return task.Result.Result;
                 }
             }
             return null;
@@ -404,21 +377,18 @@ namespace Masuit.Tools.Net
             {
                 using (var client = new HttpClient { BaseAddress = new Uri("http://ip.taobao.com") })
                 {
-                    try
+                    var task = client.GetAsync($"/service/getIpInfo.php?ip={ip}").ContinueWith(async t =>
                     {
-                        var result = client.GetStringAsync($"/service/getIpInfo.php?ip={ip}").Result;
-                        TaobaoIP taobaoIp = JsonConvert.DeserializeObject<TaobaoIP>(result);
+                        var result = await t;
+                        TaobaoIP taobaoIp = JsonConvert.DeserializeObject<TaobaoIP>(await result.Content.ReadAsStringAsync());
                         if (taobaoIp.Code == 0)
                         {
                             return taobaoIp.IpData.Isp;
                         }
-                    }
-                    catch
-                    {
-                        // ignored
-                    }
+                        return $"未能找到{ip}的ISP信息";
+                    });
+                    return task.Result.Result;
                 }
-                return $"未能找到{ip}的ISP信息";
             }
             return $"{ip}不是一个合法的IP";
         }

+ 1 - 1
Masuit.Tools/Properties/AssemblyInfo.cs

@@ -36,7 +36,7 @@ using System.Runtime.InteropServices;
 // 方法是按如下所示使用“*”: :
 // [assembly: AssemblyVersion("1.0.*")]
 
-[assembly: AssemblyVersion("1.9.4.1")]
+[assembly: AssemblyVersion("1.9.5.0")]
 [assembly: AssemblyFileVersion("1.9.4.1")]
 [assembly: NeutralResourcesLanguage("zh-CN")]
 

+ 1 - 1
Masuit.Tools/packages.config

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
   <package id="AngleSharp" version="0.9.9.2" targetFramework="net45" />
-  <package id="HtmlSanitizer" version="4.0.183" targetFramework="net45" />
+  <package id="HtmlSanitizer" version="4.0.185" targetFramework="net45" />
   <package id="Newtonsoft.Json" version="11.0.1" targetFramework="net45" />
   <package id="SharpZipLib" version="0.86.0" targetFramework="net45" />
   <package id="StackExchange.Redis" version="1.2.4" targetFramework="net45" />