Make the downloader switchable.
This commit is contained in:
parent
197305eb19
commit
2652895ba0
8 changed files with 269 additions and 65 deletions
23
README.md
23
README.md
|
|
@ -72,6 +72,29 @@ Replace the paths in `volumes` as in the above step.
|
|||
1. Same goes for `Mapped path`, but this is the destination path from your docker mapping. This is a path on your host.
|
||||
1. Save your settings.
|
||||
|
||||
### Download Clients
|
||||
|
||||
Currently there 2 available download clients:
|
||||
|
||||
#### Simple Downloader
|
||||
|
||||
This is a simple 1 connection only download manager. It uses less resources than the multi-part downloader. It downloads straight to the download path.
|
||||
|
||||
It has the following options:
|
||||
|
||||
- Maximum parallel downloads: This number indicates how many completed torrents from Real Debrid can be downloaded at the same time. On low powered systems it is recommended to keep this number low.
|
||||
|
||||
#### Multi Part Downloader
|
||||
|
||||
This [downloader](https://github.com/bezzad/Downloader) as more options and such uses more resources (memory, CPU) to download files. Recommended more powerful systems.
|
||||
|
||||
It has the following options:
|
||||
|
||||
- Temp Download path: Set this path to where the downloader temporarily stores chunks. This path can be an internal path in Docker (i.e. `/data/temp`) but make sure you have enough disk space to complete the whole download. When all chunks are completed the completed file is copied to your download folder.
|
||||
- Maximum parallel downloads: This number indicates how many completed torrents from Real Debrid can be downloaded at the same time.
|
||||
- Parallel connections per download: This number indicates how many threads/connections/parts/chunks it will use per download. This can increase speed, recommended is no more than 8.
|
||||
- Download speed (in MB/s): This number indicates the speed in MB/s per download. If you set this to 10 and `Maximum parallel downloads` to 2, you can download with a maximum of 20MB/s.
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
- If you forgot your logins simply delete the `rdtclient.db` and restart the service.
|
||||
|
|
|
|||
|
|
@ -38,7 +38,22 @@
|
|||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Temp path</label>
|
||||
<label class="label">Download client</label>
|
||||
<div class="control select is-fullwidth">
|
||||
<select [(ngModel)]="settingDownloadClient">
|
||||
<option value="Simple">Simple Downloader</option>
|
||||
<option value="MultiPart">Multi Part Downloader</option>
|
||||
<!--<option value="Aria2c">Aria2c Downloader</option>-->
|
||||
</select>
|
||||
</div>
|
||||
<p class="help">
|
||||
Select which download client to use, see the
|
||||
<a href="https://github.com/rogerfar/rdt-client/" target="_blank">README</a> for the various options.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field" *ngIf="settingDownloadClient === 'MultiPart'">
|
||||
<label class="label">Temp Download path</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" maxlength="1000" [(ngModel)]="settingTempPath" />
|
||||
</div>
|
||||
|
|
@ -56,7 +71,7 @@
|
|||
<p class="help">Maximum amount of torrents that get downloaded to your host at the same time.</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="field" *ngIf="settingDownloadClient === 'MultiPart'">
|
||||
<label class="label">Parallel connections per download</label>
|
||||
<div class="control">
|
||||
<input class="input" type="number" max="100" min="0" [(ngModel)]="settingDownloadChunkCount" />
|
||||
|
|
@ -67,7 +82,7 @@
|
|||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="field" *ngIf="settingDownloadClient === 'MultiPart'">
|
||||
<label class="label">Download speed (in MB/s)</label>
|
||||
<div class="control">
|
||||
<input class="input" type="number" max="1000" min="0" [(ngModel)]="settingDownloadMaxSpeed" />
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ export class SettingsComponent implements OnInit {
|
|||
public settingDownloadPath: string;
|
||||
public settingMappedPath: string;
|
||||
public settingTempPath: string;
|
||||
public settingDownloadClient: string;
|
||||
public settingDownloadLimit: number;
|
||||
public settingDownloadChunkCount: number;
|
||||
public settingDownloadMaxSpeed: number;
|
||||
|
|
@ -59,6 +60,7 @@ export class SettingsComponent implements OnInit {
|
|||
this.settingDownloadPath = this.getSetting(results, 'DownloadPath');
|
||||
this.settingMappedPath = this.getSetting(results, 'MappedPath');
|
||||
this.settingTempPath = this.getSetting(results, 'TempPath');
|
||||
this.settingDownloadClient = this.getSetting(results, 'DownloadClient');
|
||||
this.settingDownloadLimit = parseInt(this.getSetting(results, 'DownloadLimit'), 10);
|
||||
this.settingDownloadChunkCount = parseInt(this.getSetting(results, 'DownloadChunkCount'), 10);
|
||||
this.settingDownloadMaxSpeed = parseInt(this.getSetting(results, 'DownloadMaxSpeed'), 10);
|
||||
|
|
@ -92,6 +94,10 @@ export class SettingsComponent implements OnInit {
|
|||
settingId: 'TempPath',
|
||||
value: this.settingTempPath,
|
||||
},
|
||||
{
|
||||
settingId: 'DownloadClient',
|
||||
value: this.settingDownloadClient,
|
||||
},
|
||||
{
|
||||
settingId: 'DownloadLimit',
|
||||
value: (this.settingDownloadLimit ?? 10).toString(),
|
||||
|
|
|
|||
|
|
@ -57,6 +57,12 @@ namespace RdtClient.Data.Data
|
|||
#endif
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "DownloadClient",
|
||||
Type = "String",
|
||||
Value = @"Simple"
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "TempPath",
|
||||
Type = "String",
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using Downloader;
|
||||
using RdtClient.Data.Models.Data;
|
||||
using RdtClient.Service.Helpers;
|
||||
|
||||
namespace RdtClient.Service.Services
|
||||
{
|
||||
|
|
@ -17,6 +18,7 @@ namespace RdtClient.Service.Services
|
|||
private readonly Torrent _torrent;
|
||||
|
||||
private DownloadService _downloader;
|
||||
private SimpleDownloader _simpleDownloader;
|
||||
|
||||
public DownloadClient(Download download, String destinationPath)
|
||||
{
|
||||
|
|
@ -33,10 +35,7 @@ namespace RdtClient.Service.Services
|
|||
public Int64 BytesTotal { get; private set; }
|
||||
public Int64 BytesDone { get; private set; }
|
||||
|
||||
private Int64 LastTick { get; set; }
|
||||
private ConcurrentBag<Double> AverageSpeed { get; } = new ConcurrentBag<Double>();
|
||||
|
||||
public async Task Start(Boolean onTheFlyDownload, String tempDirectory, Int32 chunkCount, Int64 maximumBytesPerSecond)
|
||||
public async Task Start(IList<Setting> settings)
|
||||
{
|
||||
BytesDone = 0;
|
||||
BytesTotal = 0;
|
||||
|
|
@ -44,6 +43,8 @@ namespace RdtClient.Service.Services
|
|||
|
||||
try
|
||||
{
|
||||
var downloadClientSetting = settings.GetString("DownloadClient");
|
||||
|
||||
var fileUrl = _download.Link;
|
||||
|
||||
if (String.IsNullOrWhiteSpace(fileUrl))
|
||||
|
|
@ -69,7 +70,17 @@ namespace RdtClient.Service.Services
|
|||
|
||||
await Task.Factory.StartNew(async delegate
|
||||
{
|
||||
await Download(filePath, onTheFlyDownload, tempDirectory, chunkCount, maximumBytesPerSecond);
|
||||
switch (downloadClientSetting)
|
||||
{
|
||||
case "Simple":
|
||||
await DownloadSimple(uri, filePath);
|
||||
break;
|
||||
case "MultiPart":
|
||||
await DownloadMultiPart(filePath, settings);
|
||||
break;
|
||||
default:
|
||||
throw new Exception($"Unknown download client {downloadClientSetting}");
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -82,24 +93,82 @@ namespace RdtClient.Service.Services
|
|||
public void Cancel()
|
||||
{
|
||||
_downloader?.CancelAsync();
|
||||
_simpleDownloader?.Cancel();
|
||||
}
|
||||
|
||||
private async Task Download(String filePath, Boolean onTheFlyDownload, String tempDirectory, Int32 chunkCount, Int64 maximumBytesPerSecond)
|
||||
private async Task DownloadSimple(Uri uri, String filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
LastTick = 0;
|
||||
_simpleDownloader = new SimpleDownloader();
|
||||
|
||||
_simpleDownloader.DownloadProgressChanged += (_, args) =>
|
||||
{
|
||||
Speed = (Int64) args.BytesPerSecondSpeed;
|
||||
BytesDone = args.ReceivedBytesSize;
|
||||
BytesTotal = args.TotalBytesToReceive;
|
||||
};
|
||||
|
||||
_simpleDownloader.DownloadFileCompleted += (_, args) =>
|
||||
{
|
||||
if (args.Cancelled)
|
||||
{
|
||||
Error = $"The download was cancelled";
|
||||
}
|
||||
else if (args.Error != null)
|
||||
{
|
||||
Error = args.Error.Message;
|
||||
}
|
||||
|
||||
Finished = true;
|
||||
};
|
||||
|
||||
Speed = 0;
|
||||
BytesDone = 0;
|
||||
BytesTotal = 0;
|
||||
|
||||
await _simpleDownloader.Download(uri, filePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Error = $"An unexpected error occurred downloading {_download.Link} for torrent {_torrent.RdName}: {ex.Message}";
|
||||
Finished = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DownloadMultiPart(String filePath, IList<Setting> settings)
|
||||
{
|
||||
try
|
||||
{
|
||||
var settingTempPath = settings.GetString("TempPath");
|
||||
if (String.IsNullOrWhiteSpace(settingTempPath))
|
||||
{
|
||||
settingTempPath = Path.GetTempPath();
|
||||
}
|
||||
|
||||
var settingDownloadChunkCount = settings.GetNumber("DownloadChunkCount");
|
||||
if (settingDownloadChunkCount <= 0)
|
||||
{
|
||||
settingDownloadChunkCount = 1;
|
||||
}
|
||||
|
||||
var settingDownloadMaxSpeed = settings.GetNumber("DownloadMaxSpeed");
|
||||
if (settingDownloadMaxSpeed <= 0)
|
||||
{
|
||||
settingDownloadMaxSpeed = 0;
|
||||
}
|
||||
settingDownloadMaxSpeed = settingDownloadMaxSpeed * 1024 * 1024;
|
||||
|
||||
var downloadOpt = new DownloadConfiguration
|
||||
{
|
||||
MaxTryAgainOnFailover = Int32.MaxValue,
|
||||
ParallelDownload = chunkCount > 1,
|
||||
ChunkCount = chunkCount,
|
||||
ParallelDownload = settingDownloadChunkCount > 1,
|
||||
ChunkCount = settingDownloadChunkCount,
|
||||
Timeout = 1000,
|
||||
OnTheFlyDownload = onTheFlyDownload,
|
||||
OnTheFlyDownload = false,
|
||||
BufferBlockSize = 1024 * 8,
|
||||
MaximumBytesPerSecond = maximumBytesPerSecond,
|
||||
TempDirectory = tempDirectory,
|
||||
MaximumBytesPerSecond = settingDownloadMaxSpeed,
|
||||
TempDirectory = settingTempPath,
|
||||
RequestConfiguration =
|
||||
{
|
||||
Accept = "*/*",
|
||||
|
|
@ -115,31 +184,11 @@ namespace RdtClient.Service.Services
|
|||
|
||||
_downloader.DownloadProgressChanged += (_, args) =>
|
||||
{
|
||||
var isElapsedTimeMoreThanOneSecond = Environment.TickCount - LastTick >= 1000;
|
||||
|
||||
if (isElapsedTimeMoreThanOneSecond)
|
||||
{
|
||||
AverageSpeed.Add(args.BytesPerSecondSpeed);
|
||||
LastTick = Environment.TickCount;
|
||||
}
|
||||
|
||||
if (AverageSpeed.Count > 0)
|
||||
{
|
||||
Speed = (Int64) AverageSpeed.Average();
|
||||
}
|
||||
|
||||
Speed = (Int64) args.BytesPerSecondSpeed;
|
||||
BytesDone = args.ReceivedBytesSize;
|
||||
BytesTotal = args.TotalBytesToReceive;
|
||||
};
|
||||
|
||||
_downloader.DownloadStarted += (_, args) =>
|
||||
{
|
||||
AverageSpeed?.Clear();
|
||||
Speed = 0;
|
||||
BytesDone = 0;
|
||||
BytesTotal = args.TotalBytesToReceive;
|
||||
};
|
||||
|
||||
|
||||
_downloader.DownloadFileCompleted += (_, args) =>
|
||||
{
|
||||
if (args.Cancelled)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
|
@ -30,10 +29,7 @@ namespace RdtClient.Service.Services
|
|||
{
|
||||
_settingData = settingData;
|
||||
}
|
||||
|
||||
private static Int64 LastTick { get; set; }
|
||||
private static ConcurrentBag<Int64> AverageSpeed { get; } = new ConcurrentBag<Int64>();
|
||||
|
||||
|
||||
public async Task<IList<Setting>> GetAll()
|
||||
{
|
||||
return await _settingData.GetAll();
|
||||
|
|
@ -91,7 +87,8 @@ namespace RdtClient.Service.Services
|
|||
public async Task<Double> TestDownloadSpeed(CancellationToken cancellationToken)
|
||||
{
|
||||
var downloadPath = await GetString("DownloadPath");
|
||||
var tempPath = await GetString("TempPath");
|
||||
|
||||
var settings = await GetAll();
|
||||
|
||||
var testFilePath = Path.Combine(downloadPath, "testDefault.rar");
|
||||
|
||||
|
|
@ -111,7 +108,7 @@ namespace RdtClient.Service.Services
|
|||
|
||||
var downloadClient = new DownloadClient(download, downloadPath);
|
||||
|
||||
await downloadClient.Start(false, tempPath, 8, 0);
|
||||
await downloadClient.Start(settings);
|
||||
|
||||
while (!downloadClient.Finished)
|
||||
{
|
||||
|
|
|
|||
127
server/RdtClient.Service/Services/SimpleDownloader.cs
Normal file
127
server/RdtClient.Service/Services/SimpleDownloader.cs
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using DownloadProgressChangedEventArgs = Downloader.DownloadProgressChangedEventArgs;
|
||||
|
||||
namespace RdtClient.Service.Services
|
||||
{
|
||||
public class SimpleDownloader
|
||||
{
|
||||
public Int64 Speed { get; private set; }
|
||||
public Int64 BytesTotal { get; private set; }
|
||||
public Int64 BytesDone { get; private set; }
|
||||
|
||||
public event EventHandler<AsyncCompletedEventArgs> DownloadFileCompleted;
|
||||
public event EventHandler<DownloadProgressChangedEventArgs> DownloadProgressChanged;
|
||||
|
||||
private Boolean _cancelled = false;
|
||||
|
||||
private Int64 _bytesLastUpdate;
|
||||
private DateTime _nextUpdate;
|
||||
|
||||
public async Task Download(Uri uri, String filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
_bytesLastUpdate = 0;
|
||||
_nextUpdate = DateTime.UtcNow.AddSeconds(1);
|
||||
|
||||
// Determine the file size
|
||||
var webRequest = WebRequest.Create(uri);
|
||||
webRequest.Method = "HEAD";
|
||||
webRequest.Timeout = 5000;
|
||||
Int64 responseLength;
|
||||
|
||||
using (var webResponse = await webRequest.GetResponseAsync())
|
||||
{
|
||||
responseLength = Int64.Parse(webResponse.Headers.Get("Content-Length"));
|
||||
}
|
||||
|
||||
var timeout = DateTimeOffset.UtcNow.AddHours(1);
|
||||
|
||||
while (timeout > DateTimeOffset.UtcNow && !_cancelled)
|
||||
{
|
||||
try
|
||||
{
|
||||
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);
|
||||
|
||||
DownloadProgressChanged?.Invoke(this, new DownloadProgressChangedEventArgs(null)
|
||||
{
|
||||
BytesPerSecondSpeed = Speed,
|
||||
ProgressedByteSize = _bytesLastUpdate,
|
||||
TotalBytesToReceive = BytesTotal,
|
||||
AverageBytesPerSecondSpeed = Speed,
|
||||
ReceivedBytesSize = BytesDone,
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
catch (WebException)
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
}
|
||||
|
||||
if (timeout <= DateTimeOffset.UtcNow)
|
||||
{
|
||||
throw new Exception($"Download timed out");
|
||||
}
|
||||
|
||||
DownloadFileCompleted?.Invoke(this, new AsyncCompletedEventArgs(null, false, null));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DownloadFileCompleted?.Invoke(this, new AsyncCompletedEventArgs(ex, false, null));
|
||||
}
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
_cancelled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -96,25 +96,6 @@ namespace RdtClient.Service.Services
|
|||
return;
|
||||
}
|
||||
|
||||
var settingTempPath = settings.GetString("TempPath");
|
||||
if (String.IsNullOrWhiteSpace(settingTempPath))
|
||||
{
|
||||
settingTempPath = Path.GetTempPath();
|
||||
}
|
||||
|
||||
var settingDownloadChunkCount = settings.GetNumber("DownloadChunkCount");
|
||||
if (settingDownloadChunkCount <= 0)
|
||||
{
|
||||
settingDownloadChunkCount = 1;
|
||||
}
|
||||
|
||||
var settingDownloadMaxSpeed = settings.GetNumber("DownloadMaxSpeed");
|
||||
if (settingDownloadMaxSpeed <= 0)
|
||||
{
|
||||
settingDownloadMaxSpeed = 0;
|
||||
}
|
||||
settingDownloadMaxSpeed = settingDownloadMaxSpeed * 1024 * 1024;
|
||||
|
||||
// Check if any torrents are finished downloading to the host, remove them from the active download list.
|
||||
var completedActiveDownloads = ActiveDownloadClients.Where(m => m.Value.Finished).ToList();
|
||||
|
||||
|
|
@ -213,7 +194,7 @@ namespace RdtClient.Service.Services
|
|||
|
||||
if (TorrentRunner.ActiveDownloadClients.TryAdd(download.DownloadId, downloadClient))
|
||||
{
|
||||
await downloadClient.Start(false, settingTempPath, settingDownloadChunkCount, settingDownloadMaxSpeed);
|
||||
await downloadClient.Start(settings);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue