Add regex option to include or exclude files.

This commit is contained in:
Roger Far 2024-02-12 21:39:02 -07:00
parent ed01900f69
commit b0823f596d
17 changed files with 805 additions and 21 deletions

View file

@ -31,7 +31,7 @@
></textarea>
</div>
</div>
<div class="field">
<div class="control">
<button class="button is-success" [disabled]="saving" [ngClass]="{ 'is-loading': saving }" (click)="ok()">
@ -45,7 +45,7 @@
<div class="field">
<label class="label">Downloader</label>
<div class="control select is-fullwidth">
<select [(ngModel)]="downloadClient" (ngModelChange)="setFinishAction() ">
<select [(ngModel)]="downloadClient" (ngModelChange)="setFinishAction()">
<option [ngValue]="0">Internal Downloader</option>
<option [ngValue]="1">Aria2c</option>
<option [ngValue]="2">Symlink Downloader</option>
@ -117,6 +117,41 @@
</p>
<p class="help" *ngIf="downloadAction === 2">This setting does not apply to manually selected files.</p>
</div>
<div class="field">
<label class="label">Include files</label>
<div class="control">
<div class="field" style="margin-bottom: 0">
<div class="control is-expanded">
<input class="input" type="text" [(ngModel)]="includeRegex" (blur)="verifyRegex()" />
</div>
</div>
</div>
<p class="help">
Select only the files that are matching this regular expression. Only use this setting OR the Exclude files
setting, not both.
</p>
<p class="help" *ngIf="downloadAction === 2">This setting does not apply to manually selected files.</p>
<p class="help is-danger" *ngIf="includeRegexError">{{ includeRegexError }}</p>
</div>
<div class="field">
<label class="label">Exclude files</label>
<div class="control">
<div class="field" style="margin-bottom: 0">
<div class="control is-expanded">
<input class="input" type="text" [(ngModel)]="excludeRegex" (blur)="verifyRegex()" />
</div>
</div>
</div>
<p class="help">
Ignore files that are matching this regular expression. Only use this setting OR the Include files setting, not
both.
</p>
<p class="help" *ngIf="downloadAction === 2">This setting does not apply to manually selected files.</p>
<p class="help is-danger" *ngIf="excludeRegexError">{{ excludeRegexError }}</p>
</div>
<div class="field">
<label class="label">Finished action</label>
<div class="control select is-fullwidth">
@ -210,7 +245,7 @@
</div>
<div class="field" *ngIf="downloadAction !== 2 && availableFiles !== null">
<label class="is-fullwidth-label is-block" *ngFor="let file of availableFiles">
{{ file.filename }}
<span [ngClass]="{ 'strike-through': isRegexExcluded(file) }">{{ file.filename }}</span>
<span *ngIf="file.filesize > 0">({{ file.filesize | filesize }})</span>
</label>
</div>

View file

@ -30,15 +30,19 @@
.separator::before,
.separator::after {
content: '';
content: "";
flex: 1;
border-bottom: 1px solid darkgray;
}
.separator:not(:empty)::before {
margin-right: .25em;
margin-right: 0.25em;
}
.separator:not(:empty)::after {
margin-left: .25em;
}
margin-left: 0.25em;
}
.strike-through {
text-decoration: line-through;
}

View file

@ -22,6 +22,8 @@ export class AddNewTorrentComponent implements OnInit {
public downloadAction: number = 0;
public finishedAction: number = 0;
public downloadMinSize: number = 0;
public includeRegex: string = '';
public excludeRegex: string = '';
public torrentRetryAttempts: number = 1;
public downloadRetryAttempts: number = 3;
public torrentDeleteOnError: number = 0;
@ -35,12 +37,16 @@ export class AddNewTorrentComponent implements OnInit {
public saving = false;
public error: string;
public includeRegexError: string;
public excludeRegexError: string;
public regexSelected: TorrentFileAvailability[];
private selectedFile: File;
constructor(
private router: Router,
private torrentService: TorrentService,
private settingsService: SettingsService
private settingsService: SettingsService,
) {}
ngOnInit(): void {
@ -56,6 +62,8 @@ export class AddNewTorrentComponent implements OnInit {
settings.first((m) => m.key === 'Gui:Default:OnlyDownloadAvailableFiles')?.value === true ? 1 : 0;
this.finishedAction = settings.first((m) => m.key === 'Gui:Default:FinishedAction')?.value as number;
this.downloadMinSize = settings.first((m) => m.key === 'Gui:Default:MinFileSize')?.value as number;
this.includeRegex = settings.first((m) => m.key === 'Gui:Default:IncludeRegex')?.value as string;
this.excludeRegex = settings.first((m) => m.key === 'Gui:Default:ExcludeRegex')?.value as string;
this.torrentRetryAttempts = settings.first((m) => m.key === 'Gui:Default:TorrentRetryAttempts')?.value as number;
this.downloadRetryAttempts = settings.first((m) => m.key === 'Gui:Default:DownloadRetryAttempts')
?.value as number;
@ -68,10 +76,10 @@ export class AddNewTorrentComponent implements OnInit {
}
public setFinishAction() {
if (this.downloadClient === 2){
if (this.finishedAction === 1){
if (this.downloadClient === 2) {
if (this.finishedAction === 1) {
this.finishedAction = 3;
} else if (this.finishedAction === 2){
} else if (this.finishedAction === 2) {
this.finishedAction = 0;
}
}
@ -140,6 +148,8 @@ export class AddNewTorrentComponent implements OnInit {
torrent.downloadAction = this.downloadAction;
torrent.finishedAction = this.finishedAction;
torrent.downloadMinSize = this.downloadMinSize;
torrent.includeRegex = this.includeRegex;
torrent.excludeRegex = this.excludeRegex;
torrent.downloadManualFiles = downloadManualFiles;
torrent.priority = this.priority;
torrent.torrentRetryAttempts = this.torrentRetryAttempts;
@ -156,7 +166,7 @@ export class AddNewTorrentComponent implements OnInit {
(err) => {
this.error = err.error;
this.saving = false;
}
},
);
} else if (this.selectedFile) {
this.torrentService.uploadFile(this.selectedFile, torrent).subscribe(
@ -166,7 +176,7 @@ export class AddNewTorrentComponent implements OnInit {
(err) => {
this.error = err.error;
this.saving = false;
}
},
);
} else {
this.error = 'No magnet or file uploaded';
@ -204,7 +214,7 @@ export class AddNewTorrentComponent implements OnInit {
(err) => {
this.error = err.error;
this.saving = false;
}
},
);
} else if (this.selectedFile) {
this.torrentService.checkFiles(this.selectedFile).subscribe(
@ -218,10 +228,34 @@ export class AddNewTorrentComponent implements OnInit {
(err) => {
this.error = err.error;
this.saving = false;
}
},
);
} else {
this.saving = false;
}
}
public isRegexExcluded(file: TorrentFileAvailability): boolean {
if (this.regexSelected == null) {
return false;
}
if (this.regexSelected.find((m) => m.filename === file.filename) == null) {
return true;
}
return false;
}
public verifyRegex(): void {
this.includeRegexError = null;
this.excludeRegexError = null;
this.regexSelected = null;
this.torrentService.verifyRegex(this.includeRegex, this.excludeRegex, this.magnetLink).subscribe((result) => {
this.includeRegexError = result.includeError;
this.excludeRegexError = result.excludeError;
this.regexSelected = result.selectedFiles;
});
}
}

View file

@ -9,6 +9,8 @@ export class Torrent {
public downloadAction: number;
public finishedAction: number;
public downloadMinSize: number;
public includeRegex: string;
public excludeRegex: string;
public downloadManualFiles: string;
public added: Date;

View file

@ -13,7 +13,10 @@ export class TorrentService {
private connection: signalR.HubConnection;
constructor(private http: HttpClient, @Inject(APP_BASE_HREF) private baseHref: string) {
constructor(
private http: HttpClient,
@Inject(APP_BASE_HREF) private baseHref: string,
) {
this.connect();
}
@ -71,7 +74,7 @@ export class TorrentService {
torrentId: string,
deleteData: boolean,
deleteRdTorrent: boolean,
deleteLocalFiles: boolean
deleteLocalFiles: boolean,
): Observable<void> {
return this.http.post<void>(`${this.baseHref}Api/Torrents/Delete/${torrentId}`, {
deleteData,
@ -91,4 +94,19 @@ export class TorrentService {
public update(torrent: Torrent): Observable<void> {
return this.http.put<void>(`${this.baseHref}Api/Torrents/Update`, torrent);
}
public verifyRegex(
includeRegex: string,
excludeRegex: string,
magnetLink: string,
): Observable<{ includeError: string; excludeError: string; selectedFiles: TorrentFileAvailability[] }> {
return this.http.post<{ includeError: string; excludeError: string; selectedFiles: TorrentFileAvailability[] }>(
`${this.baseHref}Api/Torrents/VerifyRegex`,
{
includeRegex,
excludeRegex,
magnetLink,
},
);
}
}

View file

@ -89,6 +89,14 @@
<label class="label">Minimum file size to download</label>
{{ torrent.downloadMinSize }}MB
</div>
<div class="field">
<label class="label">Include files</label>
{{ torrent.includeRegex }}
</div>
<div class="field">
<label class="label">Exclude files</label>
{{ torrent.excludeRegex }}
</div>
<div class="field" *ngIf="!torrent.isFile">
<label class="label">Magnet</label>
<span [cdkCopyToClipboard]="torrent.fileOrMagnet" (click)="copied = true" *ngIf="!copied"
@ -477,7 +485,10 @@
<button class="delete" aria-label="close" (click)="updateSettingsCancel()"></button>
</header>
<section class="modal-card-body">
<p>Settings that are blank do not have the same values for each torrent. Updating a setting with a blank value will not update it.</p>
<p>
Settings that are blank do not have the same values for each torrent. Updating a setting with a blank value will
not update it.
</p>
<div class="field">
<label class="label">Downloader</label>
<div class="control select is-fullwidth">

View file

@ -94,6 +94,8 @@ public class TorrentData
DownloadAction = torrent.DownloadAction,
FinishedAction = torrent.FinishedAction,
DownloadMinSize = torrent.DownloadMinSize,
IncludeRegex = torrent.IncludeRegex,
ExcludeRegex = torrent.ExcludeRegex,
DownloadManualFiles = torrent.DownloadManualFiles,
DownloadClient = downloadClient,
FileOrMagnet = fileOrMagnetContents,

View file

@ -0,0 +1,473 @@
// <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("20240213025207_Torrent_Add_Include_Exclude")]
partial class Torrent_Add_Include_Exclude
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.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")
.IsRequired()
.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>("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<int>("DownloadClient")
.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>("ExcludeRegex")
.HasColumnType("TEXT");
b.Property<string>("FileOrMagnet")
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("FilesSelected")
.HasColumnType("TEXT");
b.Property<int>("FinishedAction")
.HasColumnType("INTEGER");
b.Property<string>("Hash")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("HostDownloadAction")
.HasColumnType("INTEGER");
b.Property<string>("IncludeRegex")
.HasColumnType("TEXT");
b.Property<bool>("IsFile")
.HasColumnType("INTEGER");
b.Property<int>("Lifetime")
.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,38 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace RdtClient.Data.Migrations
{
/// <inheritdoc />
public partial class Torrent_Add_Include_Exclude : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "ExcludeRegex",
table: "Torrents",
type: "TEXT",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "IncludeRegex",
table: "Torrents",
type: "TEXT",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ExcludeRegex",
table: "Torrents");
migrationBuilder.DropColumn(
name: "IncludeRegex",
table: "Torrents");
}
}
}

View file

@ -15,7 +15,7 @@ namespace RdtClient.Data.Migrations
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.0");
modelBuilder.HasAnnotation("ProductVersion", "8.0.1");
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
@ -314,6 +314,9 @@ namespace RdtClient.Data.Migrations
b.Property<string>("Error")
.HasColumnType("TEXT");
b.Property<string>("ExcludeRegex")
.HasColumnType("TEXT");
b.Property<string>("FileOrMagnet")
.HasColumnType("TEXT");
@ -330,6 +333,9 @@ namespace RdtClient.Data.Migrations
b.Property<int>("HostDownloadAction")
.HasColumnType("INTEGER");
b.Property<string>("IncludeRegex")
.HasColumnType("TEXT");
b.Property<bool>("IsFile")
.HasColumnType("INTEGER");

View file

@ -19,6 +19,8 @@ public class Torrent
public TorrentFinishedAction FinishedAction { get; set; }
public TorrentHostDownloadAction HostDownloadAction { get; set; }
public Int32 DownloadMinSize { get; set; }
public String? IncludeRegex { get; set; }
public String? ExcludeRegex { get; set; }
public String? DownloadManualFiles { get; set; }
public DownloadClient DownloadClient { get; set; }

View file

@ -225,6 +225,14 @@ public class DbSettingsDefaults
[Description("Files that are smaller than this setting are skipped and not downloaded. When set to 0 all files are downloaded. When downloading from Radarr or Sonarr it's recommended to keep this setting at atleast a few MB to avoid the debrid provider having to re-download the torrent.")]
public Int32 MinFileSize { get; set; } = 0;
[DisplayName("Include files")]
[Description("Select only the files that are matching this regular expression. Only use this setting OR the Exclude files setting, not both.")]
public String? IncludeRegex { get; set; }
[DisplayName("Exclude files")]
[Description("Ignore files that are matching this regular expression. Only use this setting OR the Include files setting, not both.")]
public String? ExcludeRegex { get; set; }
[DisplayName("Automatic retry torrent")]
[Description("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.")]
public Int32 TorrentRetryAttempts { get; set; } = 1;

View file

@ -96,6 +96,8 @@ public class WatchFolderChecker : BackgroundService
: TorrentDownloadAction.DownloadAll,
FinishedAction = Settings.Get.Watch.Default.FinishedAction,
DownloadMinSize = Settings.Get.Watch.Default.MinFileSize,
IncludeRegex = Settings.Get.Watch.Default.IncludeRegex,
ExcludeRegex = Settings.Get.Watch.Default.ExcludeRegex,
TorrentRetryAttempts = Settings.Get.Watch.Default.TorrentRetryAttempts,
DownloadRetryAttempts = Settings.Get.Watch.Default.DownloadRetryAttempts,
DeleteOnError = Settings.Get.Watch.Default.DeleteOnError,

View file

@ -461,6 +461,8 @@ public class QBittorrent
DownloadAction = Settings.Get.Integrations.Default.OnlyDownloadAvailableFiles ? TorrentDownloadAction.DownloadAvailableFiles : TorrentDownloadAction.DownloadAll,
FinishedAction = TorrentFinishedAction.None,
DownloadMinSize = Settings.Get.Integrations.Default.MinFileSize,
IncludeRegex = Settings.Get.Integrations.Default.IncludeRegex,
ExcludeRegex = Settings.Get.Integrations.Default.ExcludeRegex,
TorrentRetryAttempts = Settings.Get.Integrations.Default.TorrentRetryAttempts,
DownloadRetryAttempts = Settings.Get.Integrations.Default.DownloadRetryAttempts,
DeleteOnError = Settings.Get.Integrations.Default.DeleteOnError,
@ -483,6 +485,8 @@ public class QBittorrent
DownloadAction = Settings.Get.Integrations.Default.OnlyDownloadAvailableFiles ? TorrentDownloadAction.DownloadAvailableFiles : TorrentDownloadAction.DownloadAll,
FinishedAction = TorrentFinishedAction.None,
DownloadMinSize = Settings.Get.Integrations.Default.MinFileSize,
IncludeRegex = Settings.Get.Integrations.Default.IncludeRegex,
ExcludeRegex = Settings.Get.Integrations.Default.ExcludeRegex,
TorrentRetryAttempts = Settings.Get.Integrations.Default.TorrentRetryAttempts,
DownloadRetryAttempts = Settings.Get.Integrations.Default.DownloadRetryAttempts,
DeleteOnError = Settings.Get.Integrations.Default.DeleteOnError,

View file

@ -1,4 +1,5 @@
using Microsoft.Extensions.Logging;
using System.Text.RegularExpressions;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using RDNET;
using RdtClient.Data.Enums;
@ -202,6 +203,51 @@ public class RealDebridTorrentClient : ITorrentClient
Log($"Found {files.Count} files that match the minimum file size criterea", torrent);
}
if (!String.IsNullOrWhiteSpace(torrent.IncludeRegex))
{
Log($"Using regular expression {torrent.IncludeRegex} to include only files matching this regex", torrent);
var newFiles = new List<TorrentClientFile>();
foreach (var file in files)
{
if (Regex.IsMatch(file.Path, torrent.IncludeRegex))
{
Log($"* Including {file.Path}", torrent);
newFiles.Add(file);
}
else
{
Log($"* Excluding {file.Path}", torrent);
}
}
files = newFiles;
Log($"Found {files.Count} files that match the regex", torrent);
}
else if (!String.IsNullOrWhiteSpace(torrent.ExcludeRegex))
{
Log($"Using regular expression {torrent.IncludeRegex} to ignore files matching this regex", torrent);
var newFiles = new List<TorrentClientFile>();
foreach (var file in files)
{
if (!Regex.IsMatch(file.Path, torrent.ExcludeRegex))
{
Log($"* Including {file.Path}", torrent);
newFiles.Add(file);
}
else
{
Log($"* Excluding {file.Path}", torrent);
}
}
files = newFiles;
Log($"Found {files.Count} files that match the regex", torrent);
}
if (files.Count == 0)
{
Log($"Filtered all files out! Downloading ALL files instead!", torrent);

View file

@ -425,6 +425,8 @@ public class Torrents
DownloadAction = Settings.Get.Provider.Default.OnlyDownloadAvailableFiles ? TorrentDownloadAction.DownloadAvailableFiles : TorrentDownloadAction.DownloadAll,
FinishedAction = Settings.Get.Provider.Default.FinishedAction,
DownloadMinSize = Settings.Get.Provider.Default.MinFileSize,
IncludeRegex = Settings.Get.Provider.Default.IncludeRegex,
ExcludeRegex = Settings.Get.Provider.Default.ExcludeRegex,
TorrentRetryAttempts = Settings.Get.Provider.Default.TorrentRetryAttempts,
DownloadRetryAttempts = Settings.Get.Provider.Default.DownloadRetryAttempts,
DeleteOnError = Settings.Get.Provider.Default.DeleteOnError,

View file

@ -1,6 +1,8 @@
using Microsoft.AspNetCore.Authorization;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using MonoTorrent;
using RdtClient.Data.Models.TorrentClient;
using RdtClient.Service.Helpers;
using RdtClient.Service.Services;
using Torrent = RdtClient.Data.Models.Data.Torrent;
@ -216,6 +218,94 @@ public class TorrentsController : Controller
return Ok();
}
[HttpPost]
[Route("VerifyRegex")]
public async Task<ActionResult> VerifyRegex([FromForm] IFormFile? file, [FromBody] TorrentControllerVerifyRegexRequest? request)
{
if (request == null)
{
return Ok();
}
var includeError = "";
var excludeError = "";
IList<TorrentClientAvailableFile> availableFiles;
if (!String.IsNullOrWhiteSpace(request.MagnetLink))
{
var magnet = MagnetLink.Parse(request.MagnetLink);
availableFiles = await _torrents.GetAvailableFiles(magnet.InfoHash.ToHex());
}
else if (file != null)
{
var fileStream = file.OpenReadStream();
await using var memoryStream = new MemoryStream();
await fileStream.CopyToAsync(memoryStream);
var bytes = memoryStream.ToArray();
var torrent = await MonoTorrent.Torrent.LoadAsync(bytes);
availableFiles = await _torrents.GetAvailableFiles(torrent.InfoHash.ToHex());
}
else
{
return BadRequest();
}
var selectedFiles = new List<TorrentClientAvailableFile>();
if (!String.IsNullOrWhiteSpace(request.IncludeRegex))
{
foreach (var availableFile in availableFiles)
{
try
{
if (Regex.IsMatch(availableFile.Filename, request.IncludeRegex))
{
selectedFiles.Add(availableFile);
}
}
catch (Exception ex)
{
includeError = ex.Message;
}
}
}
else if (!String.IsNullOrWhiteSpace(request.ExcludeRegex))
{
foreach (var availableFile in availableFiles)
{
try
{
if (!Regex.IsMatch(availableFile.Filename, request.ExcludeRegex))
{
selectedFiles.Add(availableFile);
}
}
catch (Exception ex)
{
excludeError = ex.Message;
}
}
}
else
{
selectedFiles = availableFiles.ToList();
}
return Ok(new
{
includeError,
excludeError,
selectedFiles
});
}
}
public class TorrentControllerUploadFileRequest
@ -239,4 +329,11 @@ public class TorrentControllerDeleteRequest
public class TorrentControllerCheckFilesRequest
{
public String? MagnetLink { get; set; }
}
public class TorrentControllerVerifyRegexRequest
{
public String? IncludeRegex { get; set; }
public String? ExcludeRegex { get; set; }
public String? MagnetLink { get; set;}
}