From 19e5e5eb10622cb8495896d1365ab149ebe7821d Mon Sep 17 00:00:00 2001 From: Roger Far Date: Wed, 20 Jan 2021 10:44:59 -0700 Subject: [PATCH] Use a new Downloader from https://github.com/bezzad/Downloader, add a write and download speed test to the settings interface. --- .../navbar/settings/settings.component.html | 78 ++++++++- .../app/navbar/settings/settings.component.ts | 43 ++++- client/src/app/settings.service.ts | 8 + server/RdtClient.Data/RdtClient.Data.csproj | 10 +- .../RdtClient.Service.csproj | 3 +- .../Services/DownloadClient.cs | 164 ++++++++---------- server/RdtClient.Service/Services/Settings.cs | 125 +++++++------ .../Services/TorrentRunner.cs | 2 +- .../Controllers/SettingsController.cs | 30 ++-- server/RdtClient.Web/RdtClient.Web.csproj | 12 +- server/RdtClient.sln | 10 +- server/RdtClient.sln.DotSettings | 1 + 12 files changed, 301 insertions(+), 185 deletions(-) diff --git a/client/src/app/navbar/settings/settings.component.html b/client/src/app/navbar/settings/settings.component.html index 7694977..875831a 100644 --- a/client/src/app/navbar/settings/settings.component.html +++ b/client/src/app/navbar/settings/settings.component.html @@ -64,22 +64,84 @@ -
Error saving settings: {{ error }}
+
+ +
+ +
+ Could not test your download path
+ {{ testPathError }} +
-
- Could not test your download path
- {{ testPathError }} +
Your download path looks good!
+
+
This will check if the download folder has write permissions.
-
Successfully tested your download path
+
+ +
+ +
+ Could not test your download speed
+ {{ testDownloadSpeedError }} +
+ +
+ Download speed {{ testDownloadSpeedSuccess | filesize }}/s +
+
+
+ This will download a 10GB test file from RealDebrid. Hit cancel when you have seen enough. +
+
+ +
+ +
+ +
+ Could not test your download speed
+ {{ testWriteSpeedError }} +
+ +
+ Write speed {{ testWriteSpeedSuccess | filesize }}/s +
+
+
This will write a small file to your download folder to see how fast it can write to it.
+
+ +
Error saving settings: {{ error }}
-
diff --git a/client/src/app/navbar/settings/settings.component.ts b/client/src/app/navbar/settings/settings.component.ts index 355b9a5..dbd7efd 100644 --- a/client/src/app/navbar/settings/settings.component.ts +++ b/client/src/app/navbar/settings/settings.component.ts @@ -25,9 +25,16 @@ export class SettingsComponent implements OnInit { public saving = false; public error: string; + public testPathError: string; public testPathSuccess: boolean; + public testDownloadSpeedError: string; + public testDownloadSpeedSuccess: number; + + public testWriteSpeedError: string; + public testWriteSpeedSuccess: number; + public settingRealDebridApiKey: string; public settingDownloadPath: string; public settingMappedPath: string; @@ -100,7 +107,7 @@ export class SettingsComponent implements OnInit { ); } - public test(): void { + public testDownloadPath(): void { this.saving = true; this.testPathError = null; this.testPathSuccess = false; @@ -111,13 +118,45 @@ export class SettingsComponent implements OnInit { this.testPathSuccess = true; }, (err) => { - console.log(err); this.testPathError = err.error; this.saving = false; } ); } + public testDownloadSpeed(): void { + this.saving = true; + this.testDownloadSpeedError = null; + this.testDownloadSpeedSuccess = 0; + + this.settingsService.testDownloadSpeed().subscribe( + (result) => { + this.saving = false; + this.testDownloadSpeedSuccess = result; + }, + (err) => { + this.testDownloadSpeedError = err.error; + this.saving = false; + } + ); + } + public testWriteSpeed(): void { + this.saving = true; + this.testWriteSpeedError = null; + this.testWriteSpeedSuccess = 0; + + this.settingsService.testWriteSpeed().subscribe( + (result) => { + this.saving = false; + this.testWriteSpeedSuccess = result; + }, + (err) => { + this.testWriteSpeedError = err.error; + this.saving = false; + } + ); + } + public cancel(): void { this.isActive = false; this.openChange.emit(this.open); diff --git a/client/src/app/settings.service.ts b/client/src/app/settings.service.ts index 628ca18..4824d16 100644 --- a/client/src/app/settings.service.ts +++ b/client/src/app/settings.service.ts @@ -25,4 +25,12 @@ export class SettingsService { public testPath(path: string): Observable { return this.http.post(`/Api/Settings/TestPath`, { path }); } + + public testDownloadSpeed(): Observable { + return this.http.get(`/Api/Settings/TestDownloadSpeed`); + } + + public testWriteSpeed(): Observable { + return this.http.get(`/Api/Settings/TestWriteSpeed`); + } } diff --git a/server/RdtClient.Data/RdtClient.Data.csproj b/server/RdtClient.Data/RdtClient.Data.csproj index 00e4e11..c07459f 100644 --- a/server/RdtClient.Data/RdtClient.Data.csproj +++ b/server/RdtClient.Data/RdtClient.Data.csproj @@ -5,11 +5,11 @@ - - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/server/RdtClient.Service/RdtClient.Service.csproj b/server/RdtClient.Service/RdtClient.Service.csproj index 73f4056..3fc9781 100644 --- a/server/RdtClient.Service/RdtClient.Service.csproj +++ b/server/RdtClient.Service/RdtClient.Service.csproj @@ -12,10 +12,11 @@ - + + diff --git a/server/RdtClient.Service/Services/DownloadClient.cs b/server/RdtClient.Service/Services/DownloadClient.cs index 74e9dc5..b5e3dd1 100644 --- a/server/RdtClient.Service/Services/DownloadClient.cs +++ b/server/RdtClient.Service/Services/DownloadClient.cs @@ -1,30 +1,22 @@ using System; +using System.Collections.Concurrent; using System.IO; using System.Linq; using System.Net; using System.Threading.Tasks; +using Downloader; using RdtClient.Data.Models.Data; namespace RdtClient.Service.Services { public class DownloadClient { - public Boolean Finished { get; private set; } - - public String Error { get; private set; } - - public Int64 Speed { get; private set; } - public Int64 BytesTotal { get; private set; } - public Int64 BytesDone { get; private set; } + private readonly String _destinationPath; private readonly Download _download; - private readonly String _destinationPath; private readonly Torrent _torrent; - private Boolean _cancelled = false; - - private Int64 _bytesLastUpdate; - private DateTime _nextUpdate; + private DownloadService _downloader; public DownloadClient(Download download, String destinationPath) { @@ -33,7 +25,18 @@ namespace RdtClient.Service.Services _torrent = download.Torrent; } - public async Task Start() + public Boolean Finished { get; private set; } + + public String Error { get; private set; } + + public Int64 Speed { get; private set; } + public Int64 BytesTotal { get; private set; } + public Int64 BytesDone { get; private set; } + + private static Int64 LastTick { get; set; } + private static ConcurrentBag AverageSpeed { get; } = new ConcurrentBag(); + + public async Task Start(Boolean writeToMemory) { BytesDone = 0; BytesTotal = 0; @@ -58,7 +61,7 @@ namespace RdtClient.Service.Services var fileName = uri.Segments.Last(); var filePath = Path.Combine(torrentPath, fileName); - + if (File.Exists(filePath)) { File.Delete(filePath); @@ -66,10 +69,7 @@ namespace RdtClient.Service.Services await Task.Factory.StartNew(async delegate { - if (!_cancelled) - { - await Download(uri, filePath); - } + await Download(filePath, writeToMemory); }); } catch (Exception ex) @@ -81,98 +81,80 @@ namespace RdtClient.Service.Services public void Cancel() { - _cancelled = true; + _downloader?.CancelAsync(); } - private async Task Download(Uri uri, String filePath) + private async Task Download(String filePath, Boolean writeToMemory) { try { - _bytesLastUpdate = 0; - _nextUpdate = DateTime.UtcNow.AddSeconds(1); + LastTick = 0; - // Determine the file size - var webRequest = WebRequest.Create(uri); - webRequest.Method = "HEAD"; - webRequest.Timeout = 5000; - Int64 responseLength; - - using (var webResponse = await webRequest.GetResponseAsync()) + var downloadOpt = new DownloadConfiguration { - responseLength = Int64.Parse(webResponse.Headers.Get("Content-Length")); - } + MaxTryAgainOnFailover = Int32.MaxValue, + ParallelDownload = true, + ChunkCount = 8, + Timeout = 100, + OnTheFlyDownload = writeToMemory, + BufferBlockSize = 1024 * 8, + MaximumBytesPerSecond = 100 * 1024 * 1024, + TempDirectory = @"C:\temp", + RequestConfiguration = + { + Accept = "*/*", + UserAgent = $"rdt-client", + ProtocolVersion = HttpVersion.Version11, + AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate, + KeepAlive = true, + UseDefaultCredentials = false + } + }; - var timeout = DateTimeOffset.UtcNow.AddHours(1); + _downloader = new DownloadService(downloadOpt); - while (timeout > DateTimeOffset.UtcNow && !_cancelled) + _downloader.DownloadProgressChanged += (_, args) => { - try + var isElapsedTimeMoreThanOneSecond = Environment.TickCount - LastTick >= 1000; + + if (isElapsedTimeMoreThanOneSecond) { - var request = WebRequest.Create(uri); - using var response = await request.GetResponseAsync(); - - await using var stream = response.GetResponseStream(); - - if (stream == null) - { - throw new IOException("No stream"); - } - - await using var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Write); - var buffer = new Byte[64 * 1024]; - - while (fileStream.Length < response.ContentLength && !_cancelled) - { - var read = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length)); - - if (read > 0) - { - fileStream.Write(buffer, 0, read); - - BytesDone = fileStream.Length; - BytesTotal = responseLength; - - if (DateTime.UtcNow > _nextUpdate) - { - Speed = fileStream.Length - _bytesLastUpdate; - - _nextUpdate = DateTime.UtcNow.AddSeconds(1); - _bytesLastUpdate = fileStream.Length; - - timeout = DateTimeOffset.UtcNow.AddHours(1); - } - } - else - { - break; - } - } - - break; + AverageSpeed.Add(args.BytesPerSecondSpeed); + LastTick = Environment.TickCount; } - catch (IOException) - { - await Task.Delay(1000); - } - catch (WebException) - { - await Task.Delay(1000); - } - } - if (timeout <= DateTimeOffset.UtcNow) + Speed = (Int64) AverageSpeed.Average(); + BytesDone = args.BytesReceived; + BytesTotal = args.TotalBytesToReceive; + }; + + _downloader.DownloadStarted += (_, args) => { - throw new Exception($"Download timed out"); - } + AverageSpeed?.Clear(); + Speed = 0; + BytesDone = 0; + BytesTotal = args.TotalBytesToReceive; + }; - Speed = 0; + _downloader.DownloadFileCompleted += (_, args) => + { + if (args.Cancelled) + { + Error = $"The download was cancelled"; + } + else if (args.Error != null) + { + Error = args.Error.Message; + } + + Finished = true; + }; + + await _downloader.DownloadFileAsync(_download.Link, filePath); } catch (Exception ex) { Error = $"An unexpected error occurred downloading {_download.Link} for torrent {_torrent.RdName}: {ex.Message}"; - } - finally - { Finished = true; } } diff --git a/server/RdtClient.Service/Services/Settings.cs b/server/RdtClient.Service/Services/Settings.cs index bd562f3..9606a99 100644 --- a/server/RdtClient.Service/Services/Settings.cs +++ b/server/RdtClient.Service/Services/Settings.cs @@ -1,8 +1,9 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Net; +using System.Threading; using System.Threading.Tasks; using RdtClient.Data.Data; using RdtClient.Data.Models.Data; @@ -16,8 +17,8 @@ namespace RdtClient.Service.Services Task GetString(String key); Task GetNumber(String key); Task TestPath(String path); - Task TestDownloadSpeed(); - Task TestWriteSpeed(String path); + Task TestDownloadSpeed(CancellationToken cancellationToken); + Task TestWriteSpeed(); } public class Settings : ISettings @@ -29,6 +30,9 @@ namespace RdtClient.Service.Services _settingData = settingData; } + private static Int64 LastTick { get; set; } + private static ConcurrentBag AverageSpeed { get; } = new ConcurrentBag(); + public async Task> GetAll() { return await _settingData.GetAll(); @@ -83,78 +87,93 @@ namespace RdtClient.Service.Services File.Delete(testFile); } - public async Task TestDownloadSpeed() + public async Task TestDownloadSpeed(CancellationToken cancellationToken) { - var watch = new Stopwatch(); + var downloadPath = await GetString("DownloadPath"); - var request = WebRequest.Create(new Uri("https://34.download.real-debrid.com/speedtest/testDefault.rar/" + DateTime.Now.Ticks)); - - watch.Start(); - - using var response = await request.GetResponseAsync(); - - await using var stream = response.GetResponseStream(); - - if (stream == null) - { - throw new IOException("No stream"); - } - - var buffer = new Byte[64 * 1024]; - Int64 totalRead = 0; - - while (totalRead < response.ContentLength) - { - var read = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length)); - - if (read > 0) - { - totalRead += read; - } - else - { - break; - } - } - - watch.Stop(); - - var downloadSpeed = totalRead / watch.Elapsed.TotalSeconds; - - return downloadSpeed; - } - - public async Task TestWriteSpeed(String path) - { - var testFilePath = Path.Combine(path, "test.tmp"); - - const Int32 testFileSize = 1024 * 1024 * 1024; - - var watch = new Stopwatch(); + var testFilePath = Path.Combine(downloadPath, "testDefault.rar"); if (File.Exists(testFilePath)) { File.Delete(testFilePath); } - + + var download = new Download + { + Link = "https://34.download.real-debrid.com/speedtest/testDefault.rar", + Torrent = new Torrent + { + RdName = "" + } + }; + + var downloadClient = new DownloadClient(download, downloadPath); + + await downloadClient.Start(true); + + while (!downloadClient.Finished) + { + await Task.Delay(1000); + + if (cancellationToken.IsCancellationRequested) + { + downloadClient.Cancel(); + } + + if (downloadClient.BytesDone > 1024 * 1024 * 100) + { + downloadClient.Cancel(); + } + } + + if (File.Exists(testFilePath)) + { + File.Delete(testFilePath); + } + + return downloadClient.Speed; + } + + public async Task TestWriteSpeed() + { + var downloadPath = await GetString("DownloadPath"); + + var testFilePath = Path.Combine(downloadPath, "test.tmp"); + + if (File.Exists(testFilePath)) + { + File.Delete(testFilePath); + } + + const Int32 testFileSize = 64 * 1024 * 1024; + + var watch = new Stopwatch(); + watch.Start(); var rnd = new Random(); - + await using var fileStream = new FileStream(testFilePath, FileMode.Create, FileAccess.Write, FileShare.Write); var buffer = new Byte[64 * 1024]; - + while (fileStream.Length < testFileSize) { rnd.NextBytes(buffer); fileStream.Write(buffer, 0, buffer.Length); } - + watch.Stop(); var writeSpeed = fileStream.Length / watch.Elapsed.TotalSeconds; + + fileStream.Close(); + + if (File.Exists(testFilePath)) + { + File.Delete(testFilePath); + } return writeSpeed; } diff --git a/server/RdtClient.Service/Services/TorrentRunner.cs b/server/RdtClient.Service/Services/TorrentRunner.cs index 53e76c0..6478028 100644 --- a/server/RdtClient.Service/Services/TorrentRunner.cs +++ b/server/RdtClient.Service/Services/TorrentRunner.cs @@ -172,7 +172,7 @@ namespace RdtClient.Service.Services if (TorrentRunner.ActiveDownloadClients.TryAdd(download.DownloadId, downloadClient)) { - await downloadClient.Start(); + await downloadClient.Start(false); } } diff --git a/server/RdtClient.Web/Controllers/SettingsController.cs b/server/RdtClient.Web/Controllers/SettingsController.cs index 83065c7..b4ae12e 100644 --- a/server/RdtClient.Web/Controllers/SettingsController.cs +++ b/server/RdtClient.Web/Controllers/SettingsController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -58,24 +59,21 @@ namespace RdtClient.Web.Controllers } [HttpGet] - [Route("TestSpeed")] - public async Task TestSpeed() + [Route("TestDownloadSpeed")] + public async Task TestDownloadSpeed(CancellationToken cancellationToken) { - var downloadPath = await _settings.GetString("DownloadPath"); - var mappedPath = await _settings.GetString("MappedPath"); + var downloadSpeed = await _settings.TestDownloadSpeed(cancellationToken); + + return Ok(downloadSpeed); + } + + [HttpGet] + [Route("TestWriteSpeed")] + public async Task TestWriteSpeed() + { + var writeSpeed = await _settings.TestWriteSpeed(); - var downloadSpeed = await _settings.TestDownloadSpeed(); - var containerWriteSpeed = await _settings.TestWriteSpeed(downloadPath); - var remoteWriteSpeed = await _settings.TestWriteSpeed(mappedPath); - - var result = new - { - downloadSpeed, - containerWriteSpeed, - remoteWriteSpeed - }; - - return Ok(result); + return Ok(writeSpeed); } } diff --git a/server/RdtClient.Web/RdtClient.Web.csproj b/server/RdtClient.Web/RdtClient.Web.csproj index 5334df7..ad95c5e 100644 --- a/server/RdtClient.Web/RdtClient.Web.csproj +++ b/server/RdtClient.Web/RdtClient.Web.csproj @@ -7,16 +7,16 @@ - - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/server/RdtClient.sln b/server/RdtClient.sln index 6afaf9e..a408420 100644 --- a/server/RdtClient.sln +++ b/server/RdtClient.sln @@ -5,9 +5,11 @@ VisualStudioVersion = 16.0.29911.84 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RdtClient.Web", "RdtClient.Web\RdtClient.Web.csproj", "{A8C7F095-89C6-4CD1-AFB2-27106F470D62}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RdtClient.Service", "RdtClient.Service\RdtClient.Service.csproj", "{06D27710-AE9A-4FA2-B043-1A4303561716}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RdtClient.Service", "RdtClient.Service\RdtClient.Service.csproj", "{06D27710-AE9A-4FA2-B043-1A4303561716}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RdtClient.Data", "RdtClient.Data\RdtClient.Data.csproj", "{92EF8817-AD73-4301-93BD-745D7D61DD74}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RdtClient.Data", "RdtClient.Data\RdtClient.Data.csproj", "{92EF8817-AD73-4301-93BD-745D7D61DD74}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Downloader", "Downloader\Downloader.csproj", "{48FF995D-AD14-4E41-BC69-7A144C84E3B6}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -27,6 +29,10 @@ Global {92EF8817-AD73-4301-93BD-745D7D61DD74}.Debug|Any CPU.Build.0 = Debug|Any CPU {92EF8817-AD73-4301-93BD-745D7D61DD74}.Release|Any CPU.ActiveCfg = Release|Any CPU {92EF8817-AD73-4301-93BD-745D7D61DD74}.Release|Any CPU.Build.0 = Release|Any CPU + {48FF995D-AD14-4E41-BC69-7A144C84E3B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {48FF995D-AD14-4E41-BC69-7A144C84E3B6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {48FF995D-AD14-4E41-BC69-7A144C84E3B6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {48FF995D-AD14-4E41-BC69-7A144C84E3B6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/server/RdtClient.sln.DotSettings b/server/RdtClient.sln.DotSettings index 671f10a..8f514b9 100644 --- a/server/RdtClient.sln.DotSettings +++ b/server/RdtClient.sln.DotSettings @@ -1,5 +1,6 @@  False + ExplicitlyExcluded ExplicitlyExcluded *.appxmanifest *.d.ts