Add option to change the category instead of deleting queue items (#602)

This commit is contained in:
Flaminel
2026-05-05 15:59:42 +03:00
committed by GitHub
parent 48c36fab8f
commit ab792f5fad
45 changed files with 3321 additions and 203 deletions

View File

@@ -19,6 +19,7 @@ export class DocumentationService {
'failedImport.maxStrikes': 'failed-import-max-strikes',
'failedImport.ignorePrivate': 'failed-import-ignore-private',
'failedImport.deletePrivate': 'failed-import-delete-private',
'failedImport.changeCategory': 'failed-import-change-category',
'failedImport.skipIfNotFoundInClient': 'failed-import-skip-if-not-found-in-client',
'failedImport.pattern-mode': 'failed-import-pattern-mode',
'failedImport.patterns': 'failed-import-patterns',
@@ -31,6 +32,7 @@ export class DocumentationService {
'stallRule.resetStrikesOnProgress': 'stalled-reset-strikes-on-progress',
'stallRule.minimumProgress': 'stalled-minimum-progress-to-reset',
'stallRule.deletePrivateTorrentsFromClient': 'stalled-delete-private-from-client',
'stallRule.changeCategory': 'stalled-change-category',
'slowRule.name': 'slow-rule-name',
'slowRule.enabled': 'slow-enabled',
'slowRule.maxStrikes': 'slow-max-strikes',
@@ -41,6 +43,7 @@ export class DocumentationService {
'slowRule.ignoreAboveSize': 'slow-ignore-above-size',
'slowRule.resetStrikesOnProgress': 'slow-reset-strikes-on-progress',
'slowRule.deletePrivateTorrentsFromClient': 'slow-delete-private-from-client',
'slowRule.changeCategory': 'slow-change-category',
},
'general': {
'displaySupportBanner': 'display-support-banner',

View File

@@ -72,10 +72,16 @@
[disabled]="failedSubFieldsDisabled()"
hint="When enabled, private torrents will not be checked for being failed imports"
helpKey="queue-cleaner:failedImport.ignorePrivate" />
<app-toggle label="Delete Private from Client" [(checked)]="failedDeletePrivate"
[disabled]="failedDeletePrivateDisabled()"
hint="Disable this if you want to keep private torrents in the download client even if they are removed from the arrs"
helpKey="queue-cleaner:failedImport.deletePrivate" />
<app-toggle label="Change category instead of delete" [(checked)]="failedChangeCategory"
[disabled]="failedSubFieldsDisabled()"
hint="Changes the category to the post-import category set in your arr"
helpKey="queue-cleaner:failedImport.changeCategory" />
@if (!failedChangeCategory()) {
<app-toggle label="Delete Private from Client" [(checked)]="failedDeletePrivate"
[disabled]="failedDeletePrivateDisabled()"
hint="Disable this if you want to keep private torrents in the download client even if they are removed from the arrs"
helpKey="queue-cleaner:failedImport.deletePrivate" />
}
<app-toggle label="Skip if Not Found in Client" [(checked)]="failedSkipNotFound"
[disabled]="failedSubFieldsDisabled()"
hint="Skip failed import check for torrents not found in any enabled torrent client"
@@ -275,10 +281,15 @@
hint="Only reset strikes after the torrent downloads at least this amount. Leave blank to reset on any progress."
helpKey="queue-cleaner:stallRule.minimumProgress" />
}
<app-toggle label="Delete Private from Client" [(checked)]="stallDeletePrivate"
[disabled]="stallPrivacyType() === 'Public'"
hint="Disable this if you want to keep private torrents in the download client even if they are removed from the arrs"
helpKey="queue-cleaner:stallRule.deletePrivateTorrentsFromClient" />
<app-toggle label="Change category instead of delete" [(checked)]="stallChangeCategory"
hint="Changes the category to the post-import category set in your arr"
helpKey="queue-cleaner:stallRule.changeCategory" />
@if (!stallChangeCategory()) {
<app-toggle label="Delete Private from Client" [(checked)]="stallDeletePrivate"
[disabled]="stallPrivacyType() === 'Public'"
hint="Disable this if you want to keep private torrents in the download client even if they are removed from the arrs"
helpKey="queue-cleaner:stallRule.deletePrivateTorrentsFromClient" />
}
</div>
<div modal-footer>
<app-button variant="secondary" (clicked)="stallModalVisible.set(false)">Cancel</app-button>
@@ -326,10 +337,15 @@
<app-toggle label="Reset Strikes on Progress" [(checked)]="slowResetOnProgress"
hint="Reset strike count when torrent shows progress"
helpKey="queue-cleaner:slowRule.resetStrikesOnProgress" />
<app-toggle label="Delete Private from Client" [(checked)]="slowDeletePrivate"
[disabled]="slowPrivacyType() === 'Public'"
hint="Disable this if you want to keep private torrents in the download client even if they are removed from the arrs"
helpKey="queue-cleaner:slowRule.deletePrivateTorrentsFromClient" />
<app-toggle label="Change category instead of delete" [(checked)]="slowChangeCategory"
hint="Changes the category to the post-import category set in your arr"
helpKey="queue-cleaner:slowRule.changeCategory" />
@if (!slowChangeCategory()) {
<app-toggle label="Delete Private from Client" [(checked)]="slowDeletePrivate"
[disabled]="slowPrivacyType() === 'Public'"
hint="Disable this if you want to keep private torrents in the download client even if they are removed from the arrs"
helpKey="queue-cleaner:slowRule.deletePrivateTorrentsFromClient" />
}
</div>
<div modal-footer>
<app-button variant="secondary" (clicked)="slowModalVisible.set(false)">Cancel</app-button>

View File

@@ -98,6 +98,7 @@ export class QueueCleanerComponent implements OnInit, HasPendingChanges {
readonly failedSkipNotFound = signal(false);
readonly failedPatterns = signal<string[]>([]);
readonly failedPatternMode = signal<unknown>(PatternMode.Exclude);
readonly failedChangeCategory = signal(false);
readonly failedExpanded = signal(true);
// Metadata
@@ -121,6 +122,7 @@ export class QueueCleanerComponent implements OnInit, HasPendingChanges {
readonly stallResetOnProgress = signal(false);
readonly stallMinProgress = signal('');
readonly stallDeletePrivate = signal(false);
readonly stallChangeCategory = signal(false);
// Slow rules
readonly slowRules = signal<SlowRule[]>([]);
@@ -141,6 +143,7 @@ export class QueueCleanerComponent implements OnInit, HasPendingChanges {
readonly slowIgnoreAboveSize = signal('');
readonly slowResetOnProgress = signal(false);
readonly slowDeletePrivate = signal(false);
readonly slowChangeCategory = signal(false);
constructor() {
effect(() => {
@@ -158,6 +161,24 @@ export class QueueCleanerComponent implements OnInit, HasPendingChanges {
untracked(() => this.failedDeletePrivate.set(false));
}
});
effect(() => {
if (this.failedChangeCategory()) {
untracked(() => this.failedDeletePrivate.set(false));
}
});
effect(() => {
if (this.stallChangeCategory()) {
untracked(() => this.stallDeletePrivate.set(false));
}
});
effect(() => {
if (this.slowChangeCategory()) {
untracked(() => this.slowDeletePrivate.set(false));
}
});
}
// Validation
@@ -300,6 +321,7 @@ export class QueueCleanerComponent implements OnInit, HasPendingChanges {
this.failedSkipNotFound.set(config.failedImport.skipIfNotFoundInClient);
this.failedPatterns.set(config.failedImport.patterns ?? []);
this.failedPatternMode.set(config.failedImport.patternMode ?? PatternMode.Exclude);
this.failedChangeCategory.set(config.failedImport.changeCategory ?? false);
this.metadataMaxStrikes.set(config.downloadingMetadataMaxStrikes);
this.loader.stop();
this.savedSnapshot.set(this.buildSnapshot());
@@ -360,6 +382,7 @@ export class QueueCleanerComponent implements OnInit, HasPendingChanges {
this.stallResetOnProgress.set(rule.resetStrikesOnProgress);
this.stallMinProgress.set(rule.minimumProgress ?? '');
this.stallDeletePrivate.set(rule.deletePrivateTorrentsFromClient);
this.stallChangeCategory.set(rule.changeCategory ?? false);
} else {
this.stallName.set('');
this.stallEnabled.set(true);
@@ -370,6 +393,7 @@ export class QueueCleanerComponent implements OnInit, HasPendingChanges {
this.stallResetOnProgress.set(false);
this.stallMinProgress.set('');
this.stallDeletePrivate.set(false);
this.stallChangeCategory.set(false);
}
this.stallModalVisible.set(true);
}
@@ -377,6 +401,7 @@ export class QueueCleanerComponent implements OnInit, HasPendingChanges {
saveStallRule(): void {
if (this.stallNameError() || this.stallMaxStrikesError() || this.stallCompletionError()) return;
const changeCategory = this.stallChangeCategory();
const dto: CreateStallRuleDto = {
name: this.stallName().trim(),
enabled: this.stallEnabled(),
@@ -386,7 +411,8 @@ export class QueueCleanerComponent implements OnInit, HasPendingChanges {
maxCompletionPercentage: this.stallMaxCompletion() ?? 100,
resetStrikesOnProgress: this.stallResetOnProgress(),
minimumProgress: this.stallMinProgress().trim() || null,
deletePrivateTorrentsFromClient: this.stallDeletePrivate(),
deletePrivateTorrentsFromClient: changeCategory ? false : this.stallDeletePrivate(),
changeCategory,
};
const editing = this.editingStallRule();
@@ -436,6 +462,7 @@ export class QueueCleanerComponent implements OnInit, HasPendingChanges {
this.slowIgnoreAboveSize.set(rule.ignoreAboveSize ?? '');
this.slowResetOnProgress.set(rule.resetStrikesOnProgress);
this.slowDeletePrivate.set(rule.deletePrivateTorrentsFromClient);
this.slowChangeCategory.set(rule.changeCategory ?? false);
} else {
this.slowName.set('');
this.slowEnabled.set(true);
@@ -448,6 +475,7 @@ export class QueueCleanerComponent implements OnInit, HasPendingChanges {
this.slowIgnoreAboveSize.set('');
this.slowResetOnProgress.set(false);
this.slowDeletePrivate.set(false);
this.slowChangeCategory.set(false);
}
this.slowModalVisible.set(true);
}
@@ -455,6 +483,7 @@ export class QueueCleanerComponent implements OnInit, HasPendingChanges {
saveSlowRule(): void {
if (this.slowNameError() || this.slowMaxStrikesError() || this.slowCompletionError()) return;
const changeCategory = this.slowChangeCategory();
const dto: CreateSlowRuleDto = {
name: this.slowName().trim(),
enabled: this.slowEnabled(),
@@ -466,7 +495,8 @@ export class QueueCleanerComponent implements OnInit, HasPendingChanges {
minSpeed: this.slowMinSpeed().trim(),
maxTimeHours: this.slowMaxTimeHours() ?? 0,
ignoreAboveSize: this.slowIgnoreAboveSize().trim() || undefined,
deletePrivateTorrentsFromClient: this.slowDeletePrivate(),
deletePrivateTorrentsFromClient: changeCategory ? false : this.slowDeletePrivate(),
changeCategory,
};
const editing = this.editingSlowRule();
@@ -519,10 +549,11 @@ export class QueueCleanerComponent implements OnInit, HasPendingChanges {
failedImport: {
maxStrikes: this.failedMaxStrikes() ?? 3,
ignorePrivate: this.failedIgnorePrivate(),
deletePrivate: this.failedDeletePrivate(),
deletePrivate: this.failedChangeCategory() ? false : this.failedDeletePrivate(),
skipIfNotFoundInClient: this.failedSkipNotFound(),
patterns: this.failedPatterns(),
patternMode: this.failedPatternMode() as PatternMode,
changeCategory: this.failedChangeCategory(),
},
downloadingMetadataMaxStrikes: this.metadataMaxStrikes() ?? 3,
};
@@ -558,6 +589,7 @@ export class QueueCleanerComponent implements OnInit, HasPendingChanges {
failedSkipNotFound: this.failedSkipNotFound(),
failedPatterns: this.failedPatterns(),
failedPatternMode: this.failedPatternMode(),
failedChangeCategory: this.failedChangeCategory(),
metadataMaxStrikes: this.metadataMaxStrikes(),
});
}

View File

@@ -13,6 +13,7 @@ export interface FailedImportConfig {
skipIfNotFoundInClient: boolean;
patterns: string[];
patternMode?: PatternMode;
changeCategory: boolean;
}
export interface QueueCleanerConfig {

View File

@@ -9,6 +9,7 @@ export interface QueueRule {
minCompletionPercentage: number;
maxCompletionPercentage: number;
deletePrivateTorrentsFromClient: boolean;
changeCategory: boolean;
}
export interface StallRule extends QueueRule {
@@ -32,6 +33,7 @@ export interface CreateStallRuleDto {
maxCompletionPercentage: number;
resetStrikesOnProgress: boolean;
deletePrivateTorrentsFromClient: boolean;
changeCategory: boolean;
minimumProgress?: string | null;
}
@@ -47,4 +49,5 @@ export interface CreateSlowRuleDto {
maxTimeHours: number;
ignoreAboveSize?: string;
deletePrivateTorrentsFromClient: boolean;
changeCategory: boolean;
}