Rename folder to path everywhere.

Added a remote path setting for path mapping for Sonarr/Radarr.
This commit is contained in:
Roger Far 2021-01-12 21:24:46 -07:00
parent 2300b51ede
commit 7f9d6e6cba
11 changed files with 160 additions and 101 deletions

View file

@ -18,11 +18,21 @@
</p> </p>
</div> </div>
<div class="field"> <div class="field">
<label class="label">Download folder</label> <label class="label">Download path</label>
<div class="control"> <div class="control">
<input class="input" type="text" maxlength="1000" [(ngModel)]="settingDownloadFolder" /> <input class="input" type="text" maxlength="1000" [(ngModel)]="settingDownloadPath" />
</div> </div>
<p class="help">Path on the host where RealDebrid Client is hosted.</p> <p class="help">Path in the docker container to download files to (i.e. /data/downloads).</p>
</div>
<div class="field">
<label class="label">Mapped path</label>
<div class="control">
<input class="input" type="text" maxlength="1000" [(ngModel)]="settingMappedPath" />
</div>
<p class="help">
Path where files are downloaded to on your host (i.e. D:\Downloads). This path is used for Radarr and Sonarr
to find your downloads.
</p>
</div> </div>
<div class="field"> <div class="field">
<label class="label">Maximum parallel downloads</label> <label class="label">Maximum parallel downloads</label>
@ -38,16 +48,30 @@
</div> </div>
<p class="help">Maximum amount of downloads that get unpacked on your host at the same time.</p> <p class="help">Maximum amount of downloads that get unpacked on your host at the same time.</p>
</div> </div>
<div class="field">
<label class="label">Minimum file size to download</label>
<div class="control">
<div class="field has-addons" style="margin-bottom: 0">
<div class="control is-expanded">
<input class="input" type="number" max="100" min="1" [(ngModel)]="settingMinFileSize" />
</div>
<div class="control">
<a class="button is-static">MB</a>
</div>
</div>
<div class="help">When selecting files in the torrent, skip files smaller than this setting.</div>
</div>
</div>
<div class="notification is-danger is-light" *ngIf="error?.length > 0">Error saving settings: {{ error }}</div> <div class="notification is-danger is-light" *ngIf="error?.length > 0">Error saving settings: {{ error }}</div>
<div class="notification is-danger is-light" *ngIf="testFolderError?.length > 0"> <div class="notification is-danger is-light" *ngIf="testPathError?.length > 0">
Could not test your download folder<br /> Could not test your download path<br />
{{ testFolderError }} {{ testPathError }}
</div> </div>
<div class="notification is-success is-light" *ngIf="testFolderSuccess"> <div class="notification is-success is-light" *ngIf="testPathSuccess">Successfully tested your download path</div>
Successfully tested your download folder
</div>
</section> </section>
<footer class="modal-card-foot"> <footer class="modal-card-foot">
<button class="button is-success" (click)="ok()" [disabled]="saving" [ngClass]="{ 'is-loading': saving }"> <button class="button is-success" (click)="ok()" [disabled]="saving" [ngClass]="{ 'is-loading': saving }">

View file

@ -25,13 +25,15 @@ export class SettingsComponent implements OnInit {
public saving = false; public saving = false;
public error: string; public error: string;
public testFolderError: string; public testPathError: string;
public testFolderSuccess: boolean; public testPathSuccess: boolean;
public settingRealDebridApiKey: string; public settingRealDebridApiKey: string;
public settingDownloadFolder: string; public settingDownloadPath: string;
public settingMappedPath: string;
public settingDownloadLimit: number; public settingDownloadLimit: number;
public settingUnpackLimit: number; public settingUnpackLimit: number;
public settingMinFileSize: number;
constructor(private settingsService: SettingsService) {} constructor(private settingsService: SettingsService) {}
@ -44,9 +46,11 @@ export class SettingsComponent implements OnInit {
this.settingsService.get().subscribe( this.settingsService.get().subscribe(
(results) => { (results) => {
this.settingRealDebridApiKey = this.getSetting(results, 'RealDebridApiKey'); this.settingRealDebridApiKey = this.getSetting(results, 'RealDebridApiKey');
this.settingDownloadFolder = this.getSetting(results, 'DownloadFolder'); this.settingDownloadPath = this.getSetting(results, 'DownloadPath');
this.settingMappedPath = this.getSetting(results, 'MappedPath');
this.settingDownloadLimit = parseInt(this.getSetting(results, 'DownloadLimit'), 10); this.settingDownloadLimit = parseInt(this.getSetting(results, 'DownloadLimit'), 10);
this.settingUnpackLimit = parseInt(this.getSetting(results, 'UnpackLimit'), 10); this.settingUnpackLimit = parseInt(this.getSetting(results, 'UnpackLimit'), 10);
this.settingMinFileSize = parseInt(this.getSetting(results, 'MinFileSize'), 10);
}, },
(err) => { (err) => {
this.error = err.error; this.error = err.error;
@ -64,8 +68,12 @@ export class SettingsComponent implements OnInit {
value: this.settingRealDebridApiKey, value: this.settingRealDebridApiKey,
}, },
{ {
settingId: 'DownloadFolder', settingId: 'DownloadPath',
value: this.settingDownloadFolder, value: this.settingDownloadPath,
},
{
settingId: 'MappedPath',
value: this.settingMappedPath,
}, },
{ {
settingId: 'DownloadLimit', settingId: 'DownloadLimit',
@ -75,6 +83,10 @@ export class SettingsComponent implements OnInit {
settingId: 'UnpackLimit', settingId: 'UnpackLimit',
value: (this.settingUnpackLimit ?? 1).toString(), value: (this.settingUnpackLimit ?? 1).toString(),
}, },
{
settingId: 'MinFileSize',
value: (this.settingMinFileSize ?? 0).toString(),
},
]; ];
this.settingsService.update(settings).subscribe( this.settingsService.update(settings).subscribe(
@ -90,17 +102,17 @@ export class SettingsComponent implements OnInit {
public test(): void { public test(): void {
this.saving = true; this.saving = true;
this.testFolderError = null; this.testPathError = null;
this.testFolderSuccess = false; this.testPathSuccess = false;
this.settingsService.testFolder(this.settingDownloadFolder).subscribe( this.settingsService.testPath(this.settingDownloadPath).subscribe(
() => { () => {
this.saving = false; this.saving = false;
this.testFolderSuccess = true; this.testPathSuccess = true;
}, },
(err) => { (err) => {
console.log(err); console.log(err);
this.testFolderError = err.error; this.testPathError = err.error;
this.saving = false; this.saving = false;
} }
); );

View file

@ -22,7 +22,7 @@ export class SettingsService {
return this.http.get<Profile>(`/Api/Settings/Profile`); return this.http.get<Profile>(`/Api/Settings/Profile`);
} }
public testFolder(folder: string): Observable<void> { public testPath(path: string): Observable<void> {
return this.http.post<void>(`/Api/Settings/TestFolder`, { folder }); return this.http.post<void>(`/Api/Settings/TestPath`, { path });
} }
} }

View file

@ -13,69 +13,71 @@ export class TorrentStatusPipe implements PipeTransform {
return 'Finished'; return 'Finished';
} }
const allFinished = torrent.downloads.all((m) => m.completed != null); if (torrent.downloads.length > 0) {
const allFinished = torrent.downloads.all((m) => m.completed != null);
if (allFinished) { if (allFinished) {
return 'Finished'; return 'Finished';
}
const errors = torrent.downloads.where((m) => m.error != null);
if (errors.length > 0) {
return 'Error';
}
const downloading = torrent.downloads.where((m) => m.downloadStarted && !m.downloadFinished);
if (downloading.length > 0) {
const bytesDone = downloading.sum((m) => m.bytesDone);
const bytesTotal = downloading.sum((m) => m.bytesTotal);
let progress = (bytesDone / bytesTotal) * 100;
let allSpeeds = downloading.sum((m) => m.speed) / downloading.length;
let speed: string | string[] = '0';
if (allSpeeds > 0) {
speed = this.pipe.transform(allSpeeds, 'filesize');
return `Downloading (${progress.toFixed(2)}% - ${speed}/s)`;
} }
}
const unpacking = torrent.downloads.where((m) => m.unpackingStarted && !m.unpackingFinished); const errors = torrent.downloads.where((m) => m.error != null);
if (unpacking.length > 0) { if (errors.length > 0) {
const bytesDone = unpacking.sum((m) => m.bytesDone); return 'Error';
const bytesTotal = unpacking.sum((m) => m.bytesTotal);
let progress = (bytesDone / bytesTotal) * 100;
let allSpeeds = unpacking.sum((m) => m.speed) / unpacking.length;
if (allSpeeds > 0) {
return `Extracting (${progress.toFixed(2)}%)`;
} }
}
const queuedForUnpacking = torrent.downloads.where((m) => m.unpackingQueued && !m.unpackingStarted); const downloading = torrent.downloads.where((m) => m.downloadStarted && !m.downloadFinished);
if (queuedForUnpacking.length > 0) { if (downloading.length > 0) {
return `Queued for unpacking`; const bytesDone = downloading.sum((m) => m.bytesDone);
} const bytesTotal = downloading.sum((m) => m.bytesTotal);
let progress = (bytesDone / bytesTotal) * 100;
let allSpeeds = downloading.sum((m) => m.speed) / downloading.length;
const queuedForDownload = torrent.downloads.where((m) => m.downloadQueued && !m.downloadStarted); let speed: string | string[] = '0';
if (allSpeeds > 0) {
speed = this.pipe.transform(allSpeeds, 'filesize');
if (queuedForDownload.length > 0) { return `Downloading (${progress.toFixed(2)}% - ${speed}/s)`;
return `Queued for downloading`; }
} }
const unpacked = torrent.downloads.where((m) => m.unpackingFinished != null); const unpacking = torrent.downloads.where((m) => m.unpackingStarted && !m.unpackingFinished);
if (unpacked.length > 0) { if (unpacking.length > 0) {
return `Files unpacked`; const bytesDone = unpacking.sum((m) => m.bytesDone);
} const bytesTotal = unpacking.sum((m) => m.bytesTotal);
let progress = (bytesDone / bytesTotal) * 100;
let allSpeeds = unpacking.sum((m) => m.speed) / unpacking.length;
const downloaded = torrent.downloads.where((m) => m.downloadFinished != null); if (allSpeeds > 0) {
return `Extracting (${progress.toFixed(2)}%)`;
}
}
if (downloaded.length > 0) { const queuedForUnpacking = torrent.downloads.where((m) => m.unpackingQueued && !m.unpackingStarted);
return `Files downloaded to host`;
if (queuedForUnpacking.length > 0) {
return `Queued for unpacking`;
}
const queuedForDownload = torrent.downloads.where((m) => m.downloadQueued && !m.downloadStarted);
if (queuedForDownload.length > 0) {
return `Queued for downloading`;
}
const unpacked = torrent.downloads.where((m) => m.unpackingFinished != null);
if (unpacked.length > 0) {
return `Files unpacked`;
}
const downloaded = torrent.downloads.where((m) => m.downloadFinished != null);
if (downloaded.length > 0) {
return `Files downloaded to host`;
}
} }
switch (torrent.rdStatus) { switch (torrent.rdStatus) {
@ -89,7 +91,7 @@ export class TorrentStatusPipe implements PipeTransform {
case RealDebridStatus.Error: case RealDebridStatus.Error:
return `Torrent error: ${torrent.rdStatusRaw}`; return `Torrent error: ${torrent.rdStatusRaw}`;
case RealDebridStatus.Finished: case RealDebridStatus.Finished:
return `Torrent finished`; return `Torrent finished, waiting to download`;
default: default:
return 'Unknown status'; return 'Unknown status';
} }

View file

@ -48,7 +48,7 @@ namespace RdtClient.Data.Data
}, },
new Setting new Setting
{ {
SettingId = "DownloadFolder", SettingId = "DownloadPath",
Type = "String", Type = "String",
#if DEBUG #if DEBUG
Value = @"C:\Temp\rdtclient" Value = @"C:\Temp\rdtclient"
@ -57,6 +57,16 @@ namespace RdtClient.Data.Data
#endif #endif
}, },
new Setting new Setting
{
SettingId = "MappedPath",
Type = "String",
#if DEBUG
Value = @"C:\Temp\rdtclient"
#else
Value = "D:\Downloads"
#endif
},
new Setting
{ {
SettingId = "DownloadLimit", SettingId = "DownloadLimit",
Type = "Int32", Type = "Int32",
@ -67,6 +77,12 @@ namespace RdtClient.Data.Data
SettingId = "UnpackLimit", SettingId = "UnpackLimit",
Type = "Int32", Type = "Int32",
Value = "1" Value = "1"
},
new Setting
{
SettingId = "MinFileSize",
Type = "Int32",
Value = "0"
} }
}; };

View file

@ -213,7 +213,7 @@ namespace RdtClient.Service.Services
public async Task<String> AppDefaultSavePath() public async Task<String> AppDefaultSavePath()
{ {
var downloadPath = await _settings.GetString("DownloadFolder"); var downloadPath = await _settings.GetString("MappedPath");
downloadPath = downloadPath.TrimEnd('\\') downloadPath = downloadPath.TrimEnd('\\')
.TrimEnd('/'); .TrimEnd('/');
downloadPath += Path.DirectorySeparatorChar; downloadPath += Path.DirectorySeparatorChar;

View file

@ -13,7 +13,7 @@ namespace RdtClient.Service.Services
Task Update(IList<Setting> settings); Task Update(IList<Setting> settings);
Task<String> GetString(String key); Task<String> GetString(String key);
Task<Int32> GetNumber(String key); Task<Int32> GetNumber(String key);
Task TestFolder(String folder); Task TestPath(String path);
} }
public class Settings : ISettings public class Settings : ISettings
@ -59,21 +59,21 @@ namespace RdtClient.Service.Services
return Int32.Parse(setting.Value); return Int32.Parse(setting.Value);
} }
public async Task TestFolder(String folder) public async Task TestPath(String path)
{ {
if (String.IsNullOrWhiteSpace(folder)) if (String.IsNullOrWhiteSpace(path))
{ {
throw new Exception("Folder path is not set"); throw new Exception("Path is not set");
} }
folder = folder.TrimEnd('/').TrimEnd('\\'); path = path.TrimEnd('/').TrimEnd('\\');
if (!Directory.Exists(folder)) if (!Directory.Exists(path))
{ {
throw new Exception($"Folder {folder} does not exist"); throw new Exception($"Path {path} does not exist");
} }
var testFile = $"{folder}/test.txt"; var testFile = $"{path}/test.txt";
await File.WriteAllTextAsync(testFile, "RealDebridClient Test File, you can remove this file."); await File.WriteAllTextAsync(testFile, "RealDebridClient Test File, you can remove this file.");
File.Delete(testFile); File.Delete(testFile);

View file

@ -20,12 +20,8 @@ namespace RdtClient.Service.Services
protected override async Task ExecuteAsync(CancellationToken stoppingToken) protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{ {
#if DEBUG await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken);
await Task.Delay(TimeSpan.FromSeconds(3), stoppingToken);
#else
await Task.Delay(TimeSpan.FromSeconds(15), stoppingToken);
#endif
using var scope = _serviceProvider.CreateScope(); using var scope = _serviceProvider.CreateScope();
var torrentRunner = scope.ServiceProvider.GetRequiredService<ITorrentRunner>(); var torrentRunner = scope.ServiceProvider.GetRequiredService<ITorrentRunner>();
@ -44,7 +40,7 @@ namespace RdtClient.Service.Services
_logger.LogError(ex, "Unexpected error occurred in TorrentDownloadManager.Tick"); _logger.LogError(ex, "Unexpected error occurred in TorrentDownloadManager.Tick");
} }
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken); await Task.Delay(TimeSpan.FromSeconds(2), stoppingToken);
} }
_logger.LogInformation("TaskRunner stopped."); _logger.LogInformation("TaskRunner stopped.");

View file

@ -61,6 +61,7 @@ namespace RdtClient.Service.Services
public async Task Tick() public async Task Tick()
{ {
var settingApiKey = await _settings.GetString("RealDebridApiKey"); var settingApiKey = await _settings.GetString("RealDebridApiKey");
var minFileSizeSetting = await _settings.GetNumber("MinFileSize");
if (String.IsNullOrWhiteSpace(settingApiKey)) if (String.IsNullOrWhiteSpace(settingApiKey))
{ {
@ -164,6 +165,14 @@ namespace RdtClient.Service.Services
.Select(m => m.Id.ToString()) .Select(m => m.Id.ToString())
.ToArray(); .ToArray();
if (minFileSizeSetting > 0)
{
fileIds = torrent.Files
.Where(m => m.Bytes * 1024 * 1024 > minFileSizeSetting)
.Select(m => m.Id.ToString())
.ToArray();
}
await _torrents.SelectFiles(torrent.RdId, fileIds); await _torrents.SelectFiles(torrent.RdId, fileIds);
} }

View file

@ -165,9 +165,9 @@ namespace RdtClient.Service.Services
{ {
if (deleteLocalFiles) if (deleteLocalFiles)
{ {
var settingDownloadFolder = await _settings.GetString("DownloadFolder"); var settingDownloadPath = await _settings.GetString("DownloadPath");
var torrentPath = Path.Combine(settingDownloadFolder, torrent.RdName); var torrentPath = Path.Combine(settingDownloadPath, torrent.RdName);
if (Directory.Exists(torrentPath)) if (Directory.Exists(torrentPath))
{ {
@ -210,7 +210,7 @@ namespace RdtClient.Service.Services
public async Task Download(Guid downloadId) public async Task Download(Guid downloadId)
{ {
var settingDownloadLimit = await _settings.GetNumber("DownloadLimit"); var settingDownloadLimit = await _settings.GetNumber("DownloadLimit");
var settingDownloadFolder = await _settings.GetString("DownloadFolder"); var settingDownloadPath = await _settings.GetString("DownloadPath");
var download = await _downloads.GetById(downloadId); var download = await _downloads.GetById(downloadId);
@ -224,7 +224,7 @@ namespace RdtClient.Service.Services
await _downloads.UpdateDownloadStarted(download.DownloadId, download.DownloadStarted); await _downloads.UpdateDownloadStarted(download.DownloadId, download.DownloadStarted);
// Start the download process // Start the download process
var downloadClient = new DownloadClient(download, settingDownloadFolder); var downloadClient = new DownloadClient(download, settingDownloadPath);
if (TorrentRunner.ActiveDownloadClients.TryAdd(downloadId, downloadClient)) if (TorrentRunner.ActiveDownloadClients.TryAdd(downloadId, downloadClient))
{ {
@ -235,7 +235,7 @@ namespace RdtClient.Service.Services
public async Task Unpack(Guid downloadId) public async Task Unpack(Guid downloadId)
{ {
var settingUnpackLimit = await _settings.GetNumber("UnpackLimit"); var settingUnpackLimit = await _settings.GetNumber("UnpackLimit");
var settingDownloadFolder = await _settings.GetString("DownloadFolder"); var settingDownloadPath = await _settings.GetString("DownloadPath");
var download = await _downloads.GetById(downloadId); var download = await _downloads.GetById(downloadId);
@ -270,7 +270,7 @@ namespace RdtClient.Service.Services
await _downloads.UpdateUnpackingStarted(download.DownloadId, download.UnpackingStarted); await _downloads.UpdateUnpackingStarted(download.DownloadId, download.UnpackingStarted);
// Start the unpacking process // Start the unpacking process
var unpackClient = new UnpackClient(download, settingDownloadFolder); var unpackClient = new UnpackClient(download, settingDownloadPath);
if (TorrentRunner.ActiveUnpackClients.TryAdd(downloadId, unpackClient)) if (TorrentRunner.ActiveUnpackClients.TryAdd(downloadId, unpackClient))
{ {

View file

@ -49,10 +49,10 @@ namespace RdtClient.Web.Controllers
} }
[HttpPost] [HttpPost]
[Route("TestFolder")] [Route("TestPath")]
public async Task<ActionResult<Profile>> TestFolder([FromBody] SettingsControllerTestFolderRequest request) public async Task<ActionResult<Profile>> TestPath([FromBody] SettingsControllerTestPathRequest request)
{ {
await _settings.TestFolder(request.Folder); await _settings.TestPath(request.Path);
return Ok(); return Ok();
} }
@ -63,8 +63,8 @@ namespace RdtClient.Web.Controllers
public IList<Setting> Settings { get; set; } public IList<Setting> Settings { get; set; }
} }
public class SettingsControllerTestFolderRequest public class SettingsControllerTestPathRequest
{ {
public String Folder { get; set; } public String Path { get; set; }
} }
} }