Added setting to automatically delete torrents in the state of error after a certain amount of time.

This commit is contained in:
Roger Far 2022-02-06 15:34:36 -07:00
parent 444c547b15
commit 25f8c70b17
19 changed files with 605 additions and 35 deletions

View file

@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.0.6] - 2022-02-06
### Added
- Added setting to automatically delete torrents in the state of error after a certain amount of time.
## [2.0.5] - 2022-01-11 ## [2.0.5] - 2022-01-11
### Changed ### Changed
- Updated AllDebrid provider to fix issue with ID's not being a number. - Updated AllDebrid provider to fix issue with ID's not being a number.

View file

@ -128,6 +128,16 @@
error it will retry the full torrent this many times before marking it failed. error it will retry the full torrent this many times before marking it failed.
</p> </p>
</div> </div>
<div class="field">
<label class="label">Delete download when in error</label>
<div class="control">
<input class="input" type="number" max="1000" min="0" step="1" [(ngModel)]="torrentDeleteOnError" />
</div>
<p class="help">
When a download has been in error for this many minutes, delete it from the provider and the client. 0 to
disable.
</p>
</div>
</div> </div>
<div fxFlex *ngIf="provider === 'RealDebrid'"> <div fxFlex *ngIf="provider === 'RealDebrid'">
<div class="field"> <div class="field">

View file

@ -26,6 +26,7 @@ export class AddNewTorrentComponent implements OnInit {
public downloadRetryAttempts: number = 3; public downloadRetryAttempts: number = 3;
public torrentRetryAttempts: number = 1; public torrentRetryAttempts: number = 1;
public torrentDeleteOnError: number = 0;
public availableFiles: TorrentFileAvailability[]; public availableFiles: TorrentFileAvailability[];
public downloadFiles: { [key: string]: boolean } = {}; public downloadFiles: { [key: string]: boolean } = {};
@ -36,9 +37,13 @@ export class AddNewTorrentComponent implements OnInit {
private selectedFile: File; private selectedFile: File;
constructor(private router: Router, private torrentService: TorrentService, private settingsService: SettingsService) { constructor(
this.settingsService.get().subscribe(settings => { private router: Router,
this.provider = settings.firstOrDefault(m => m.settingId === 'Provider')?.value; private torrentService: TorrentService,
private settingsService: SettingsService
) {
this.settingsService.get().subscribe((settings) => {
this.provider = settings.firstOrDefault((m) => m.settingId === 'Provider')?.value;
}); });
} }
@ -110,6 +115,7 @@ export class AddNewTorrentComponent implements OnInit {
torrent.priority = this.priority; torrent.priority = this.priority;
torrent.torrentRetryAttempts = this.torrentRetryAttempts; torrent.torrentRetryAttempts = this.torrentRetryAttempts;
torrent.downloadRetryAttempts = this.downloadRetryAttempts; torrent.downloadRetryAttempts = this.downloadRetryAttempts;
torrent.deleteOnError = this.torrentDeleteOnError;
if (this.magnetLink) { if (this.magnetLink) {
this.torrentService.uploadMagnet(this.magnetLink, torrent).subscribe( this.torrentService.uploadMagnet(this.magnetLink, torrent).subscribe(

View file

@ -19,6 +19,7 @@ export class Torrent {
public retryCount: number; public retryCount: number;
public downloadRetryAttempts: number; public downloadRetryAttempts: number;
public torrentRetryAttempts: number; public torrentRetryAttempts: number;
public deleteOnError: number;
public priority: number; public priority: number;
public error: string; public error: string;

View file

@ -122,6 +122,14 @@
</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">Maximum unpack processes</label>
<div class="control">
<input class="input" type="number" max="100" min="1" step="1" [(ngModel)]="settingUnpackLimit" />
</div>
<p class="help">Maximum amount of downloads that get unpacked on your host at the same time.</p>
</div>
</div> </div>
<div *ngIf="activeTab === 1"> <div *ngIf="activeTab === 1">
@ -189,7 +197,7 @@
<div class="field" *ngIf="settingDownloadClient === 'Aria2c'"> <div class="field" *ngIf="settingDownloadClient === 'Aria2c'">
<label class="label">Aria2c URL</label> <label class="label">Aria2c URL</label>
<div class="control"> <div class="control">
<input class="input" type="text" maxlength="1000" [(ngModel)]="aria2cUrl" /> <input class="input" type="text" maxlength="1000" [(ngModel)]="settingAria2cUrl" />
</div> </div>
<p class="help"> <p class="help">
This is the URL to your Aria2c instance. It must end in /jsonrpc. A common URL is This is the URL to your Aria2c instance. It must end in /jsonrpc. A common URL is
@ -200,7 +208,7 @@
<div class="field" *ngIf="settingDownloadClient === 'Aria2c'"> <div class="field" *ngIf="settingDownloadClient === 'Aria2c'">
<label class="label">Aria2c Secret</label> <label class="label">Aria2c Secret</label>
<div class="control"> <div class="control">
<input class="input" type="text" maxlength="1000" [(ngModel)]="aria2cSecret" /> <input class="input" type="text" maxlength="1000" [(ngModel)]="settingAria2cSecret" />
</div> </div>
<p class="help">The secret of your Aria2c instance. Optional.</p> <p class="help">The secret of your Aria2c instance. Optional.</p>
</div> </div>
@ -264,7 +272,7 @@
<div class="field"> <div class="field">
<label class="label">Automatic retry downloads</label> <label class="label">Automatic retry downloads</label>
<div class="control"> <div class="control">
<input class="input" type="number" max="1000" min="0" step="1" [(ngModel)]="downloadRetryAttempts" /> <input class="input" type="number" max="1000" min="0" step="1" [(ngModel)]="settingDownloadRetryAttempts" />
</div> </div>
<p class="help">When a single download fails it will retry it this many times before marking it as failed.</p> <p class="help">When a single download fails it will retry it this many times before marking it as failed.</p>
</div> </div>
@ -272,13 +280,23 @@
<div class="field"> <div class="field">
<label class="label">Automatic retry torrent</label> <label class="label">Automatic retry torrent</label>
<div class="control"> <div class="control">
<input class="input" type="number" max="1000" min="0" step="1" [(ngModel)]="torrentRetryAttempts" /> <input class="input" type="number" max="1000" min="0" step="1" [(ngModel)]="settingTorrentRetryAttempts" />
</div> </div>
<p class="help"> <p class="help">
When a single download has failed multiple times (see setting above) or when the torrent itself received an error When a single download has failed multiple times (see setting above) or when the torrent itself received an error
it will retry the full torrent this many times before marking it failed. it will retry the full torrent this many times before marking it failed.
</p> </p>
</div> </div>
<div class="field">
<label class="label">Delete download when in error</label>
<div class="control">
<input class="input" type="number" max="1000" min="0" step="1" [(ngModel)]="settingDeleteOnError" />
</div>
<p class="help">
When a download has been in error for this many minutes, delete it from the provider and the client. 0 to disable.
</p>
</div>
</div> </div>
<div *ngIf="activeTab === 3"> <div *ngIf="activeTab === 3">

View file

@ -41,12 +41,11 @@ export class SettingsComponent implements OnInit {
public settingMinFileSize: number; public settingMinFileSize: number;
public settingOnlyDownloadAvailableFiles: boolean; public settingOnlyDownloadAvailableFiles: boolean;
public settingProxyServer: string; public settingProxyServer: string;
public settingAria2cUrl: string;
public aria2cUrl: string; public settingAria2cSecret: string;
public aria2cSecret: string; public settingDownloadRetryAttempts: number;
public settingTorrentRetryAttempts: number;
public downloadRetryAttempts: number; public settingDeleteOnError: number;
public torrentRetryAttempts: number;
constructor(private settingsService: SettingsService) {} constructor(private settingsService: SettingsService) {}
@ -76,10 +75,11 @@ export class SettingsComponent implements OnInit {
this.settingMinFileSize = parseInt(this.getSetting(results, 'MinFileSize'), 10); this.settingMinFileSize = parseInt(this.getSetting(results, 'MinFileSize'), 10);
this.settingOnlyDownloadAvailableFiles = this.getSetting(results, 'OnlyDownloadAvailableFiles') === '1'; this.settingOnlyDownloadAvailableFiles = this.getSetting(results, 'OnlyDownloadAvailableFiles') === '1';
this.settingProxyServer = this.getSetting(results, 'ProxyServer'); this.settingProxyServer = this.getSetting(results, 'ProxyServer');
this.aria2cUrl = this.getSetting(results, 'Aria2cUrl'); this.settingAria2cUrl = this.getSetting(results, 'Aria2cUrl');
this.aria2cSecret = this.getSetting(results, 'Aria2cSecret'); this.settingAria2cSecret = this.getSetting(results, 'Aria2cSecret');
this.downloadRetryAttempts = parseInt(this.getSetting(results, 'DownloadRetryAttempts'), 10); this.settingDownloadRetryAttempts = parseInt(this.getSetting(results, 'DownloadRetryAttempts'), 10);
this.torrentRetryAttempts = parseInt(this.getSetting(results, 'TorrentRetryAttempts'), 10); this.settingTorrentRetryAttempts = parseInt(this.getSetting(results, 'TorrentRetryAttempts'), 10);
this.settingDeleteOnError = parseInt(this.getSetting(results, 'DeleteOnError'), 10);
}, },
(err) => { (err) => {
this.error = err.error; this.error = err.error;
@ -158,19 +158,23 @@ export class SettingsComponent implements OnInit {
}, },
{ {
settingId: 'Aria2cUrl', settingId: 'Aria2cUrl',
value: this.aria2cUrl, value: this.settingAria2cUrl,
}, },
{ {
settingId: 'Aria2cSecret', settingId: 'Aria2cSecret',
value: this.aria2cSecret, value: this.settingAria2cSecret,
}, },
{ {
settingId: 'DownloadRetryAttempts', settingId: 'DownloadRetryAttempts',
value: (this.downloadRetryAttempts ?? 0).toString(), value: (this.settingDownloadRetryAttempts ?? 0).toString(),
}, },
{ {
settingId: 'TorrentRetryAttempts', settingId: 'TorrentRetryAttempts',
value: (this.torrentRetryAttempts ?? 0).toString(), value: (this.settingTorrentRetryAttempts ?? 0).toString(),
},
{
settingId: 'DeleteOnError',
value: (this.settingDeleteOnError ?? 0).toString(),
}, },
]; ];
@ -242,7 +246,7 @@ export class SettingsComponent implements OnInit {
this.testAria2cConnectionError = null; this.testAria2cConnectionError = null;
this.testAria2cConnectionSuccess = null; this.testAria2cConnectionSuccess = null;
this.settingsService.testAria2cConnection(this.aria2cUrl, this.aria2cSecret).subscribe( this.settingsService.testAria2cConnection(this.settingAria2cUrl, this.settingAria2cSecret).subscribe(
(result) => { (result) => {
this.saving = false; this.saving = false;
this.testAria2cConnectionSuccess = result.version; this.testAria2cConnectionSuccess = result.version;

View file

@ -37,7 +37,7 @@
</div> </div>
<div class="field"> <div class="field">
<label class="label">Retry count</label> <label class="label">Retry count</label>
{{ torrent.retryCount }} / {{ torrent.torrentRetryAttempts}} {{ torrent.retryCount }} / {{ torrent.torrentRetryAttempts }}
</div> </div>
<div class="field"> <div class="field">
<label class="label">Hash</label> <label class="label">Hash</label>
@ -501,6 +501,16 @@
error it will retry the full torrent this many times before marking it failed. error it will retry the full torrent this many times before marking it failed.
</p> </p>
</div> </div>
<div class="field">
<label class="label">Delete download when in error</label>
<div class="control">
<input class="input" type="number" max="1000" min="0" step="1" [(ngModel)]="updateSettingsDeleteOnError" />
</div>
<p class="help">
When a download has been in error for this many minutes, delete it from the provider and the client. 0 to
disable.
</p>
</div>
</section> </section>
<footer class="modal-card-foot"> <footer class="modal-card-foot">
<button <button

View file

@ -38,6 +38,8 @@ export class TorrentComponent implements OnInit {
public updateSettingsPriority: number; public updateSettingsPriority: number;
public updateSettingsDownloadRetryAttempts: number; public updateSettingsDownloadRetryAttempts: number;
public updateSettingsTorrentRetryAttempts: number; public updateSettingsTorrentRetryAttempts: number;
public updateSettingsDeleteOnError: number;
public updating: boolean; public updating: boolean;
constructor(private activatedRoute: ActivatedRoute, private router: Router, private torrentService: TorrentService) {} constructor(private activatedRoute: ActivatedRoute, private router: Router, private torrentService: TorrentService) {}
@ -174,6 +176,7 @@ export class TorrentComponent implements OnInit {
this.updateSettingsPriority = this.torrent.priority; this.updateSettingsPriority = this.torrent.priority;
this.updateSettingsDownloadRetryAttempts = this.torrent.downloadRetryAttempts; this.updateSettingsDownloadRetryAttempts = this.torrent.downloadRetryAttempts;
this.updateSettingsTorrentRetryAttempts = this.torrent.torrentRetryAttempts; this.updateSettingsTorrentRetryAttempts = this.torrent.torrentRetryAttempts;
this.updateSettingsDeleteOnError = this.torrent.deleteOnError;
this.isUpdateSettingsModalActive = true; this.isUpdateSettingsModalActive = true;
} }
@ -188,6 +191,7 @@ export class TorrentComponent implements OnInit {
this.torrent.priority = this.updateSettingsPriority; this.torrent.priority = this.updateSettingsPriority;
this.torrent.downloadRetryAttempts = this.updateSettingsDownloadRetryAttempts; this.torrent.downloadRetryAttempts = this.updateSettingsDownloadRetryAttempts;
this.torrent.torrentRetryAttempts = this.updateSettingsTorrentRetryAttempts; this.torrent.torrentRetryAttempts = this.updateSettingsTorrentRetryAttempts;
this.torrent.deleteOnError = this.updateSettingsDeleteOnError;
this.torrentService.update(this.torrent).subscribe( this.torrentService.update(this.torrent).subscribe(
() => { () => {

View file

@ -59,6 +59,12 @@ namespace RdtClient.Data.Data
Value = "0" Value = "0"
}, },
new Setting new Setting
{
SettingId = "DeleteOnError",
Type = "Int32",
Value = "0"
},
new Setting
{ {
SettingId = "RealDebridApiKey", SettingId = "RealDebridApiKey",
Type = "String", Type = "String",

View file

@ -50,7 +50,7 @@ namespace RdtClient.Data.Data
{ {
Provider = GetString("Provider"), Provider = GetString("Provider"),
ProviderAutoImport = GetInt32("ProviderAutoImport"), ProviderAutoImport = GetInt32("ProviderAutoImport"),
ProviderAutoDelete= GetInt32("ProviderAutoDelete"), ProviderAutoDelete = GetInt32("ProviderAutoDelete"),
RealDebridApiKey = GetString("RealDebridApiKey"), RealDebridApiKey = GetString("RealDebridApiKey"),
DownloadPath = GetString("DownloadPath"), DownloadPath = GetString("DownloadPath"),
DownloadClient = GetString("DownloadClient"), DownloadClient = GetString("DownloadClient"),
@ -68,7 +68,8 @@ namespace RdtClient.Data.Data
Aria2cUrl = GetString("Aria2cUrl"), Aria2cUrl = GetString("Aria2cUrl"),
Aria2cSecret = GetString("Aria2cSecret"), Aria2cSecret = GetString("Aria2cSecret"),
DownloadRetryAttempts = GetInt32("DownloadRetryAttempts"), DownloadRetryAttempts = GetInt32("DownloadRetryAttempts"),
TorrentRetryAttempts = GetInt32("TorrentRetryAttempts") TorrentRetryAttempts = GetInt32("TorrentRetryAttempts"),
DeleteOnError = GetInt32("DeleteOnError"),
}; };
} }

View file

@ -100,7 +100,8 @@ namespace RdtClient.Data.Data
IsFile = isFile, IsFile = isFile,
Priority = torrent.Priority, Priority = torrent.Priority,
TorrentRetryAttempts = torrent.TorrentRetryAttempts, TorrentRetryAttempts = torrent.TorrentRetryAttempts,
DownloadRetryAttempts = torrent.DownloadRetryAttempts DownloadRetryAttempts = torrent.DownloadRetryAttempts,
DeleteOnError = torrent.DeleteOnError
}; };
await _dataContext.Torrents.AddAsync(newTorrent); await _dataContext.Torrents.AddAsync(newTorrent);
@ -155,6 +156,7 @@ namespace RdtClient.Data.Data
dbTorrent.Priority = torrent.Priority; dbTorrent.Priority = torrent.Priority;
dbTorrent.DownloadRetryAttempts = torrent.DownloadRetryAttempts; dbTorrent.DownloadRetryAttempts = torrent.DownloadRetryAttempts;
dbTorrent.TorrentRetryAttempts = torrent.TorrentRetryAttempts; dbTorrent.TorrentRetryAttempts = torrent.TorrentRetryAttempts;
dbTorrent.DeleteOnError = torrent.DeleteOnError;
await _dataContext.SaveChangesAsync(); await _dataContext.SaveChangesAsync();

View file

@ -0,0 +1,458 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using RdtClient.Data.Data;
#nullable disable
namespace RdtClient.Data.Migrations
{
[DbContext(typeof(DataContext))]
[Migration("20220206223254_Torrents_Add_DeleteOnError")]
partial class Torrents_Add_DeleteOnError
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "6.0.1");
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex");
b.ToTable("AspNetRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<int>("AccessFailedCount")
.HasColumnType("INTEGER");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<bool>("EmailConfirmed")
.HasColumnType("INTEGER");
b.Property<bool>("LockoutEnabled")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("TEXT");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("PasswordHash")
.HasColumnType("TEXT");
b.Property<string>("PhoneNumber")
.HasColumnType("TEXT");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("INTEGER");
b.Property<string>("SecurityStamp")
.HasColumnType("TEXT");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("INTEGER");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex");
b.ToTable("AspNetUsers", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("TEXT");
b.Property<string>("ProviderKey")
.HasColumnType("TEXT");
b.Property<string>("ProviderDisplayName")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.Property<string>("RoleId")
.HasColumnType("TEXT");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.Property<string>("LoginProvider")
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<string>("Value")
.HasColumnType("TEXT");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("RdtClient.Data.Models.Data.Download", b =>
{
b.Property<Guid>("DownloadId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("Added")
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("Completed")
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("DownloadFinished")
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("DownloadQueued")
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("DownloadStarted")
.HasColumnType("TEXT");
b.Property<string>("Error")
.HasColumnType("TEXT");
b.Property<string>("Link")
.HasColumnType("TEXT");
b.Property<string>("Path")
.HasColumnType("TEXT");
b.Property<string>("RemoteId")
.HasColumnType("TEXT");
b.Property<int>("RetryCount")
.HasColumnType("INTEGER");
b.Property<Guid>("TorrentId")
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("UnpackingFinished")
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("UnpackingQueued")
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("UnpackingStarted")
.HasColumnType("TEXT");
b.HasKey("DownloadId");
b.HasIndex("TorrentId");
b.ToTable("Downloads");
});
modelBuilder.Entity("RdtClient.Data.Models.Data.Setting", b =>
{
b.Property<string>("SettingId")
.HasColumnType("TEXT");
b.Property<string>("Type")
.HasColumnType("TEXT");
b.Property<string>("Value")
.HasColumnType("TEXT");
b.HasKey("SettingId");
b.ToTable("Settings");
});
modelBuilder.Entity("RdtClient.Data.Models.Data.Torrent", b =>
{
b.Property<Guid>("TorrentId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("Added")
.HasColumnType("TEXT");
b.Property<string>("Category")
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("Completed")
.HasColumnType("TEXT");
b.Property<int>("DeleteOnError")
.HasColumnType("INTEGER");
b.Property<int>("DownloadAction")
.HasColumnType("INTEGER");
b.Property<string>("DownloadManualFiles")
.HasColumnType("TEXT");
b.Property<int>("DownloadMinSize")
.HasColumnType("INTEGER");
b.Property<int>("DownloadRetryAttempts")
.HasColumnType("INTEGER");
b.Property<string>("Error")
.HasColumnType("TEXT");
b.Property<string>("FileOrMagnet")
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("FilesSelected")
.HasColumnType("TEXT");
b.Property<int>("FinishedAction")
.HasColumnType("INTEGER");
b.Property<string>("Hash")
.HasColumnType("TEXT");
b.Property<bool>("IsFile")
.HasColumnType("INTEGER");
b.Property<int?>("Priority")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset>("RdAdded")
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("RdEnded")
.HasColumnType("TEXT");
b.Property<string>("RdFiles")
.HasColumnType("TEXT");
b.Property<string>("RdHost")
.HasColumnType("TEXT");
b.Property<string>("RdId")
.HasColumnType("TEXT");
b.Property<string>("RdName")
.HasColumnType("TEXT");
b.Property<long>("RdProgress")
.HasColumnType("INTEGER");
b.Property<long?>("RdSeeders")
.HasColumnType("INTEGER");
b.Property<long>("RdSize")
.HasColumnType("INTEGER");
b.Property<long?>("RdSpeed")
.HasColumnType("INTEGER");
b.Property<long>("RdSplit")
.HasColumnType("INTEGER");
b.Property<int>("RdStatus")
.HasColumnType("INTEGER");
b.Property<string>("RdStatusRaw")
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("Retry")
.HasColumnType("TEXT");
b.Property<int>("RetryCount")
.HasColumnType("INTEGER");
b.Property<int>("TorrentRetryAttempts")
.HasColumnType("INTEGER");
b.HasKey("TorrentId");
b.ToTable("Torrents");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
});
modelBuilder.Entity("RdtClient.Data.Models.Data.Download", b =>
{
b.HasOne("RdtClient.Data.Models.Data.Torrent", "Torrent")
.WithMany("Downloads")
.HasForeignKey("TorrentId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("Torrent");
});
modelBuilder.Entity("RdtClient.Data.Models.Data.Torrent", b =>
{
b.Navigation("Downloads");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,26 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace RdtClient.Data.Migrations
{
public partial class Torrents_Add_DeleteOnError : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "DeleteOnError",
table: "Torrents",
type: "INTEGER",
nullable: false,
defaultValue: 0);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "DeleteOnError",
table: "Torrents");
}
}
}

View file

@ -5,6 +5,8 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using RdtClient.Data.Data; using RdtClient.Data.Data;
#nullable disable
namespace RdtClient.Data.Migrations namespace RdtClient.Data.Migrations
{ {
[DbContext(typeof(DataContext))] [DbContext(typeof(DataContext))]
@ -13,8 +15,7 @@ namespace RdtClient.Data.Migrations
protected override void BuildModel(ModelBuilder modelBuilder) protected override void BuildModel(ModelBuilder modelBuilder)
{ {
#pragma warning disable 612, 618 #pragma warning disable 612, 618
modelBuilder modelBuilder.HasAnnotation("ProductVersion", "6.0.1");
.HasAnnotation("ProductVersion", "5.0.11");
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{ {
@ -39,7 +40,7 @@ namespace RdtClient.Data.Migrations
.IsUnique() .IsUnique()
.HasDatabaseName("RoleNameIndex"); .HasDatabaseName("RoleNameIndex");
b.ToTable("AspNetRoles"); b.ToTable("AspNetRoles", (string)null);
}); });
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
@ -62,7 +63,7 @@ namespace RdtClient.Data.Migrations
b.HasIndex("RoleId"); b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims"); b.ToTable("AspNetRoleClaims", (string)null);
}); });
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
@ -126,7 +127,7 @@ namespace RdtClient.Data.Migrations
.IsUnique() .IsUnique()
.HasDatabaseName("UserNameIndex"); .HasDatabaseName("UserNameIndex");
b.ToTable("AspNetUsers"); b.ToTable("AspNetUsers", (string)null);
}); });
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
@ -149,7 +150,7 @@ namespace RdtClient.Data.Migrations
b.HasIndex("UserId"); b.HasIndex("UserId");
b.ToTable("AspNetUserClaims"); b.ToTable("AspNetUserClaims", (string)null);
}); });
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
@ -171,7 +172,7 @@ namespace RdtClient.Data.Migrations
b.HasIndex("UserId"); b.HasIndex("UserId");
b.ToTable("AspNetUserLogins"); b.ToTable("AspNetUserLogins", (string)null);
}); });
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
@ -186,7 +187,7 @@ namespace RdtClient.Data.Migrations
b.HasIndex("RoleId"); b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles"); b.ToTable("AspNetUserRoles", (string)null);
}); });
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b => modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
@ -205,7 +206,7 @@ namespace RdtClient.Data.Migrations
b.HasKey("UserId", "LoginProvider", "Name"); b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens"); b.ToTable("AspNetUserTokens", (string)null);
}); });
modelBuilder.Entity("RdtClient.Data.Models.Data.Download", b => modelBuilder.Entity("RdtClient.Data.Models.Data.Download", b =>
@ -294,6 +295,9 @@ namespace RdtClient.Data.Migrations
b.Property<DateTimeOffset?>("Completed") b.Property<DateTimeOffset?>("Completed")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<int>("DeleteOnError")
.HasColumnType("INTEGER");
b.Property<int>("DownloadAction") b.Property<int>("DownloadAction")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");

View file

@ -34,6 +34,7 @@ namespace RdtClient.Data.Models.Data
public Int32 RetryCount { get; set; } public Int32 RetryCount { get; set; }
public Int32 DownloadRetryAttempts { get; set; } public Int32 DownloadRetryAttempts { get; set; }
public Int32 TorrentRetryAttempts { get; set; } public Int32 TorrentRetryAttempts { get; set; }
public Int32 DeleteOnError { get; set; }
public String Error { get; set; } public String Error { get; set; }

View file

@ -25,5 +25,6 @@ namespace RdtClient.Data.Models.Internal
public String Aria2cSecret { get; set; } public String Aria2cSecret { get; set; }
public Int32 DownloadRetryAttempts { get; set; } public Int32 DownloadRetryAttempts { get; set; }
public Int32 TorrentRetryAttempts { get; set; } public Int32 TorrentRetryAttempts { get; set; }
public Int32 DeleteOnError { get; set; }
} }
} }

View file

@ -443,6 +443,7 @@ namespace RdtClient.Service.Services
DownloadMinSize = Settings.Get.MinFileSize, DownloadMinSize = Settings.Get.MinFileSize,
TorrentRetryAttempts = Settings.Get.TorrentRetryAttempts, TorrentRetryAttempts = Settings.Get.TorrentRetryAttempts,
DownloadRetryAttempts = Settings.Get.DownloadRetryAttempts, DownloadRetryAttempts = Settings.Get.DownloadRetryAttempts,
DeleteOnError = Settings.Get.DeleteOnError,
Priority = priority Priority = priority
}; };
@ -459,6 +460,7 @@ namespace RdtClient.Service.Services
DownloadMinSize = Settings.Get.MinFileSize, DownloadMinSize = Settings.Get.MinFileSize,
TorrentRetryAttempts = Settings.Get.TorrentRetryAttempts, TorrentRetryAttempts = Settings.Get.TorrentRetryAttempts,
DownloadRetryAttempts = Settings.Get.DownloadRetryAttempts, DownloadRetryAttempts = Settings.Get.DownloadRetryAttempts,
DeleteOnError = Settings.Get.DeleteOnError,
Priority = priority Priority = priority
}; };

View file

@ -266,6 +266,17 @@ namespace RdtClient.Service.Services
} }
} }
// Process torrent errors
foreach (var torrent in torrents.Where(m => m.Error != null && m.Completed != null && m.DeleteOnError > 0))
{
if (torrent.Completed.Value.AddMinutes(torrent.DeleteOnError) > DateTime.UtcNow)
{
continue;
}
await _torrents.Delete(torrent.TorrentId, true, true, true);
}
torrents = torrents.Where(m => m.Completed == null).ToList(); torrents = torrents.Where(m => m.Completed == null).ToList();
// Only poll Real-Debrid every second when a hub is connected, otherwise every 30 seconds // Only poll Real-Debrid every second when a hub is connected, otherwise every 30 seconds

View file

@ -366,6 +366,7 @@ namespace RdtClient.Service.Services
DownloadMinSize = 0, DownloadMinSize = 0,
TorrentRetryAttempts = 0, TorrentRetryAttempts = 0,
DownloadRetryAttempts = Settings.Get.DownloadRetryAttempts, DownloadRetryAttempts = Settings.Get.DownloadRetryAttempts,
DeleteOnError = Settings.Get.DeleteOnError,
Priority = 0, Priority = 0,
RdId = rdTorrent.Id RdId = rdTorrent.Id
}; };