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/),
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
### Changed
- 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.
</p>
</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 fxFlex *ngIf="provider === 'RealDebrid'">
<div class="field">

View file

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

View file

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

View file

@ -122,6 +122,14 @@
</div>
<p class="help">Maximum amount of downloads that get unpacked on your host at the same time.</p>
</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 *ngIf="activeTab === 1">
@ -189,7 +197,7 @@
<div class="field" *ngIf="settingDownloadClient === 'Aria2c'">
<label class="label">Aria2c URL</label>
<div class="control">
<input class="input" type="text" maxlength="1000" [(ngModel)]="aria2cUrl" />
<input class="input" type="text" maxlength="1000" [(ngModel)]="settingAria2cUrl" />
</div>
<p class="help">
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'">
<label class="label">Aria2c Secret</label>
<div class="control">
<input class="input" type="text" maxlength="1000" [(ngModel)]="aria2cSecret" />
<input class="input" type="text" maxlength="1000" [(ngModel)]="settingAria2cSecret" />
</div>
<p class="help">The secret of your Aria2c instance. Optional.</p>
</div>
@ -264,7 +272,7 @@
<div class="field">
<label class="label">Automatic retry downloads</label>
<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>
<p class="help">When a single download fails it will retry it this many times before marking it as failed.</p>
</div>
@ -272,13 +280,23 @@
<div class="field">
<label class="label">Automatic retry torrent</label>
<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>
<p class="help">
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.
</p>
</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 *ngIf="activeTab === 3">

View file

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

View file

@ -37,7 +37,7 @@
</div>
<div class="field">
<label class="label">Retry count</label>
{{ torrent.retryCount }} / {{ torrent.torrentRetryAttempts}}
{{ torrent.retryCount }} / {{ torrent.torrentRetryAttempts }}
</div>
<div class="field">
<label class="label">Hash</label>
@ -501,6 +501,16 @@
error it will retry the full torrent this many times before marking it failed.
</p>
</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>
<footer class="modal-card-foot">
<button

View file

@ -38,6 +38,8 @@ export class TorrentComponent implements OnInit {
public updateSettingsPriority: number;
public updateSettingsDownloadRetryAttempts: number;
public updateSettingsTorrentRetryAttempts: number;
public updateSettingsDeleteOnError: number;
public updating: boolean;
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.updateSettingsDownloadRetryAttempts = this.torrent.downloadRetryAttempts;
this.updateSettingsTorrentRetryAttempts = this.torrent.torrentRetryAttempts;
this.updateSettingsDeleteOnError = this.torrent.deleteOnError;
this.isUpdateSettingsModalActive = true;
}
@ -188,6 +191,7 @@ export class TorrentComponent implements OnInit {
this.torrent.priority = this.updateSettingsPriority;
this.torrent.downloadRetryAttempts = this.updateSettingsDownloadRetryAttempts;
this.torrent.torrentRetryAttempts = this.updateSettingsTorrentRetryAttempts;
this.torrent.deleteOnError = this.updateSettingsDeleteOnError;
this.torrentService.update(this.torrent).subscribe(
() => {

View file

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

View file

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

View file

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

View file

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

View file

@ -25,5 +25,6 @@ namespace RdtClient.Data.Models.Internal
public String Aria2cSecret { get; set; }
public Int32 DownloadRetryAttempts { 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,
TorrentRetryAttempts = Settings.Get.TorrentRetryAttempts,
DownloadRetryAttempts = Settings.Get.DownloadRetryAttempts,
DeleteOnError = Settings.Get.DeleteOnError,
Priority = priority
};
@ -459,6 +460,7 @@ namespace RdtClient.Service.Services
DownloadMinSize = Settings.Get.MinFileSize,
TorrentRetryAttempts = Settings.Get.TorrentRetryAttempts,
DownloadRetryAttempts = Settings.Get.DownloadRetryAttempts,
DeleteOnError = Settings.Get.DeleteOnError,
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();
// 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,
TorrentRetryAttempts = 0,
DownloadRetryAttempts = Settings.Get.DownloadRetryAttempts,
DeleteOnError = Settings.Get.DeleteOnError,
Priority = 0,
RdId = rdTorrent.Id
};