private void OnGetFileInfo(DownloadFileInfo downloadFileInfo) { try { FileInfoReceived?.Invoke(this, new FileInfoReceivedEventArgs { DownloadFileInfo = downloadFileInfo }); } catch (Exception ex) { OnExceptionThrown(ex); } }
private List <Range> PrepareRanges(DownloadFileInfo info, int numberOfParallelDownloads) { var readRanges = new List <Range>(); long lastRangeStart = 0; try { if (info.IsSupportedRange) { for (int chunk = 0; chunk < numberOfParallelDownloads - 1; chunk++) { var range = new Range { Start = chunk * (info.Length / numberOfParallelDownloads), End = ((chunk + 1) * (info.Length / numberOfParallelDownloads)) - 1 }; readRanges.Add(range); lastRangeStart = range.End + 1; } } //last range which we add always even if the Range header is not supported readRanges.Add(new Range { Start = lastRangeStart, End = info.Length - 1 }); for (int i = 0; i < readRanges.Count; i++) { readRanges[i].Index = i; readRanges[i].Buffer = new byte[readRanges[i].End - readRanges[i].Start + 1]; } } catch (Exception ex) { OnExceptionThrown(ex); } return(readRanges); }
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 DownloadFileInfo GetFileInfo(string fileUrl) { var info = new DownloadFileInfo { FileUrl = fileUrl }; int triesCount = 0; try { while (!info.IsOperationSuccess && triesCount < MaxTriesCount) { try { //we must create web request each time to prevent DNS caching var webRequest = (HttpWebRequest)WebRequest.Create(fileUrl); webRequest.Method = "HEAD"; webRequest.Timeout = TimeoutMs; webRequest.ReadWriteTimeout = TimeoutMs; using (var webResponse = webRequest.GetResponse()) { info.Length = long.Parse(webResponse.Headers.Get("Content-Length")); info.IsSupportedHead = true; info.Exists = true; info.IsOperationSuccess = true; } } catch (WebException ex) { var status = (ex.Response as HttpWebResponse)?.StatusCode; //File does not exist on server, return if (status == HttpStatusCode.Forbidden || status == HttpStatusCode.NotFound) { return(info); } log.Warn("WebException during getting HEAD", ex); WaitNetwork(ref triesCount); } catch (Exception ex) { log.Warn("Exception during getting HEAD download", ex); WaitNetwork(ref triesCount); } } info.IsOperationSuccess = false; triesCount = 0; while (!info.IsOperationSuccess && triesCount < MaxTriesCount) { try { //we must create web request each time to prevent DNS caching var webRequest = (HttpWebRequest)WebRequest.Create(fileUrl); webRequest.AddRange(0, (int)info.Length - 1); webRequest.Method = "HEAD"; webRequest.Timeout = TimeoutMs; webRequest.ReadWriteTimeout = TimeoutMs; using (var webResponse = (HttpWebResponse)webRequest.GetResponse()) { info.IsSupportedRange = webResponse.StatusCode == HttpStatusCode.PartialContent || webResponse.GetResponseHeader("Accept-Ranges") == "bytes"; info.IsOperationSuccess = true; } } catch (WebException ex) { var status = (ex.Response as HttpWebResponse)?.StatusCode; //File does not exist on server, return if (status == HttpStatusCode.Forbidden || status == HttpStatusCode.NotFound) { return(info); } log.Warn("WebException during getting HEAD", ex); WaitNetwork(ref triesCount); } catch (Exception ex) { log.Warn("Exception during getting HEAD download", ex); WaitNetwork(ref triesCount); } } } catch (Exception ex) { OnExceptionThrown(ex); } return(info); }