Add regex option to include or exclude files.
This commit is contained in:
parent
ed01900f69
commit
b0823f596d
17 changed files with 805 additions and 21 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
473
server/RdtClient.Data/Migrations/20240213025207_Torrent_Add_Include_Exclude.Designer.cs
generated
Normal file
473
server/RdtClient.Data/Migrations/20240213025207_Torrent_Add_Include_Exclude.Designer.cs
generated
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;}
|
||||
}
|
||||
Loading…
Reference in a new issue