public void OnDownloadComplete(DownloadResult result)
 {
     try
     {
         DownloadCompleted?.Invoke(this, new DownloadCompletedEventArgs {
             Result = result
         });
     }
     catch (Exception ex)
     {
         OnExceptionThrown(ex);
     }
 }
        private DownloadResult DownloadRanges(DownloadFileInfo info, List <Range> readRanges, CancellationToken cancel)
        {
            // Parallel download
            var result = new DownloadResult {
                FileUrl = info.FileUrl, FileExists = info.Exists, FileLength = info.Length, ParallelDownloads = readRanges.Count
            };
            long bytesDownloaded = 0;

            try
            {
                if (cancel.IsCancellationRequested)
                {
                    result.IsCancelled = true;
                    return(result);
                }

                int numberOfThreads          = readRanges.Count;
                var mutex                    = new ManualResetEvent(false);
                var progressChangedEventArgs = new ProgressChangedEventArgs
                {
                    FileUrl = info.FileUrl, FileLength = info.Length
                };
                StartProgressThread(progressChangedEventArgs);
                StartBandwidthThread(progressChangedEventArgs);
                var sw = Stopwatch.StartNew();

                foreach (var readRange in readRanges)
                {
                    new Thread(_ =>
                    {
                        try
                        {
                            log.Debug($"{readRange.Index} started. Range: {readRange.Start}-{readRange.End}");

                            int rangeLen        = readRange.Buffer.Length;
                            int offset          = 0;
                            const int blockSize = 4096;
                            int triesCount      = 0;
                            bool success        = false;

                            while (!success && !mutex.WaitOne(0) && triesCount < MaxTriesCount)
                            {
                                try
                                {
                                    var httpWebRequest    = (HttpWebRequest)WebRequest.Create(info.FileUrl);
                                    httpWebRequest.Method = "GET";
                                    if (info.IsSupportedRange)
                                    {
                                        httpWebRequest.AddRange((int)readRange.Start + offset, (int)readRange.End);
                                    }
                                    httpWebRequest.Timeout          = TimeoutMs;
                                    httpWebRequest.ReadWriteTimeout = TimeoutMs;
                                    using (var httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse())
                                    {
                                        using (var responseStream = httpWebResponse.GetResponseStream())
                                        {
                                            int bytesRead;
                                            while ((bytesRead = responseStream.Read(readRange.Buffer,
                                                                                    offset,
                                                                                    rangeLen - offset < blockSize ? rangeLen - offset : blockSize
                                                                                    )) > 0 && !cancel.IsCancellationRequested)
                                            {
                                                offset += bytesRead;
                                                Interlocked.Add(ref bytesDownloaded, bytesRead);

                                                lock (progressChangedEventArgs)
                                                {
                                                    progressChangedEventArgs.Progress  = bytesDownloaded;
                                                    progressChangedEventArgs.ElapsedMs = sw.ElapsedMilliseconds;
                                                    Monitor.PulseAll(progressChangedEventArgs);
                                                }
                                            }
                                        }

                                        success = true;
                                    }
                                }
                                catch (Exception ex)
                                {
                                    //reset offset if server does not support range
                                    if (!info.IsSupportedRange)
                                    {
                                        offset = 0;
                                    }

                                    log.Warn(
                                        $"Exception {ex.Message}. index={readRange.Index} offset={offset} uri={info.FileUrl}");
                                    WaitNetwork(ref triesCount);
                                }
                            }

                            //If one thread is failed signalize other threads to exit.
                            //If all threads completed signalize the method to return download result
                            if (!success || Interlocked.Decrement(ref numberOfThreads) == 0)
                            {
                                mutex.Set();
                            }

                            log.Debug($"{readRange.Index} completed. Success = {success}. Range: {readRange.Start}-{readRange.End}");
                        }
                        catch (Exception ex)
                        {
                            OnExceptionThrown(ex);
                        }
                    })
                    {
                        IsBackground = true
                    }.Start();
                }

                mutex.WaitOne();
                sw.Stop();
                result.BytesDownloaded    = bytesDownloaded;
                result.IsOperationSuccess = Interlocked.CompareExchange(ref numberOfThreads, 0, 0) == 0;
                result.IsCancelled        = cancel.IsCancellationRequested;
            }
            catch (Exception ex)
            {
                OnExceptionThrown(ex);
            }

            return(result);
        }
        public DownloadResult Download(string fileUrl, string filePath, int numberOfParallelDownloads, CancellationToken cancellationToken)
        {
            try
            {
                var uri = new Uri(fileUrl);

                var downloadFileInfo = GetFileInfo(fileUrl);
                OnGetFileInfo(downloadFileInfo);

                log.Info(
                    $"HEAD supported: {downloadFileInfo.IsSupportedHead}, Range supported: {downloadFileInfo.IsSupportedRange}");
                log.Info($"File {fileUrl} exists: {downloadFileInfo.Exists}, length = {downloadFileInfo.Length}");
                log.Info($"{downloadFileInfo.Length}");

                DownloadResult result;
                if (!downloadFileInfo.Exists || !downloadFileInfo.IsOperationSuccess)
                {
                    result = new DownloadResult
                    {
                        FileUrl            = fileUrl, FilePath = filePath, FileExists = downloadFileInfo.Exists,
                        IsOperationSuccess = downloadFileInfo.IsOperationSuccess,
                        IsCancelled        = cancellationToken.IsCancellationRequested
                    };
                    OnDownloadComplete(result);
                    return(result);
                }


                //Handle number of parallel downloads
                if (numberOfParallelDownloads <= 0)
                {
                    numberOfParallelDownloads = Environment.ProcessorCount;
                }

                var readRanges = PrepareRanges(downloadFileInfo, numberOfParallelDownloads);

                var sw = Stopwatch.StartNew();
                result = DownloadRanges(downloadFileInfo, readRanges, cancellationToken);
                sw.Stop();

                result.TimeTakenMs = sw.ElapsedMilliseconds;
                result.FilePath    = filePath;

                if (!result.IsCancelled)
                {
                    result.IsOperationSuccess &= WriteFile(filePath, readRanges);
                }

                OnDownloadComplete(result);

                return(result);
            }
            catch (Exception ex)
            {
                OnExceptionThrown(ex);
            }

            return(new DownloadResult {
                FileUrl = fileUrl, FilePath = filePath, IsOperationSuccess = false
            });
        }