Big update on the add download and setting screens which are now proper pages instead of modals.
Fixed issue where it would slow down on waiting for RealDebrid generating links for downloads.
This commit is contained in:
parent
4daa5ba4dc
commit
fa11dc9bc7
33 changed files with 1951 additions and 709 deletions
25
client/package-lock.json
generated
25
client/package-lock.json
generated
|
|
@ -291,6 +291,23 @@
|
|||
"tslib": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"@angular/cdk": {
|
||||
"version": "12.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-12.1.2.tgz",
|
||||
"integrity": "sha512-ALupZejZDsVYcbNZcEH1cV8SDgVBL40FAwDnlSZxCgd0HOBHH0ZqQV+8z0uCQeMatoNM+SwmJ8Y1JXYh9Bqfiw==",
|
||||
"requires": {
|
||||
"parse5": "^5.0.0",
|
||||
"tslib": "^2.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"parse5": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz",
|
||||
"integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@angular/cli": {
|
||||
"version": "12.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-12.1.2.tgz",
|
||||
|
|
@ -469,6 +486,14 @@
|
|||
"tslib": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"@angular/flex-layout": {
|
||||
"version": "12.0.0-beta.34",
|
||||
"resolved": "https://registry.npmjs.org/@angular/flex-layout/-/flex-layout-12.0.0-beta.34.tgz",
|
||||
"integrity": "sha512-nLwKovXpyG+/U3Lbmfwt+q4ARupxtbPmFTZD0Y8oItFAV6/Oh9l+QQsNQa2VhOHAOrVagyDwcEM+SePtB5EmrQ==",
|
||||
"requires": {
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"@angular/forms": {
|
||||
"version": "12.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-12.1.2.tgz",
|
||||
|
|
|
|||
|
|
@ -13,9 +13,11 @@
|
|||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "~12.1.2",
|
||||
"@angular/cdk": "^12.1.2",
|
||||
"@angular/common": "~12.1.2",
|
||||
"@angular/compiler": "~12.1.2",
|
||||
"@angular/core": "~12.1.2",
|
||||
"@angular/flex-layout": "^12.0.0-beta.34",
|
||||
"@angular/forms": "~12.1.2",
|
||||
"@angular/platform-browser": "~12.1.2",
|
||||
"@angular/platform-browser-dynamic": "~12.1.2",
|
||||
|
|
|
|||
123
client/src/app/add-new-torrent/add-new-torrent.component.html
Normal file
123
client/src/app/add-new-torrent/add-new-torrent.component.html
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
<div fxLayout.lt-lg="column" fxLayout.gt-sm="row" fxLayoutGap="20px" class="field">
|
||||
<div fxFlex>
|
||||
<div class="field">
|
||||
<label class="label">Torrent file</label>
|
||||
<div class="file has-name">
|
||||
<label class="file-label">
|
||||
<input class="file-input" type="file" name="resume" (change)="pickFile($event)" [disabled]="saving" />
|
||||
<span class="file-cta">
|
||||
<span class="file-icon">
|
||||
<i class="fas fa-upload"></i>
|
||||
</span>
|
||||
<span class="file-label"> Pick a torrent file... </span>
|
||||
</span>
|
||||
<span class="file-name">
|
||||
{{ fileName }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Magnet Link</label>
|
||||
<div class="control">
|
||||
<textarea
|
||||
class="textarea"
|
||||
placeholder="Paste your magnet link here"
|
||||
[(ngModel)]="magnetLink"
|
||||
[disabled]="saving"
|
||||
(blur)="checkFiles()"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Download action</label>
|
||||
<div class="control select is-fullwidth">
|
||||
<select [(ngModel)]="downloadAction">
|
||||
<option [ngValue]="0">Download all files above a certain size</option>
|
||||
<option [ngValue]="1">Download all available files on Real Debrid above a certain size</option>
|
||||
<option [ngValue]="2">Pick files I want to download</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Minimum file size to download</label>
|
||||
<div class="control">
|
||||
<div class="field has-addons" style="margin-bottom: 0">
|
||||
<div class="control is-expanded">
|
||||
<input class="input" type="number" max="1000" min="0" [(ngModel)]="downloadMinSize" />
|
||||
</div>
|
||||
<div class="control">
|
||||
<a class="button is-static">MB</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help" *ngIf="downloadAction === 2">This setting does not apply to manually selected files.</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Finished action</label>
|
||||
<div class="control select is-fullwidth">
|
||||
<select [(ngModel)]="finishedAction">
|
||||
<option [ngValue]="0">Do nothing</option>
|
||||
<option [ngValue]="1">Remove torrent from Real Debrid and Real Debrid Client</option>
|
||||
<option [ngValue]="2">Remove torrent from Real Debrid</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Category</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" maxlength="100" [(ngModel)]="category" />
|
||||
</div>
|
||||
<p class="help">The category becomes a sub-folder in your main download path.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div fxFlex>
|
||||
<div class="field">
|
||||
<label class="label">Available files</label>
|
||||
<p class="help">
|
||||
These files are available for immediate download from Real Debrid. <br />
|
||||
It is possible that there are more files in the torrent, which are not shown here.
|
||||
</p>
|
||||
<div class="scroll-container">
|
||||
<div class="field" *ngIf="downloadAction === 2">
|
||||
<label class="checkbox is-fullwidth-label" *ngFor="let file of availableFiles">
|
||||
<input
|
||||
type="checkbox"
|
||||
[checked]="downloadFiles[file.filename]"
|
||||
(change)="downloadFileChecked(file.filename)"
|
||||
/>
|
||||
{{ file.filename }} ({{ file.filesize | filesize }})
|
||||
</label>
|
||||
</div>
|
||||
<div class="field" *ngIf="downloadAction !== 2">
|
||||
<label class="is-fullwidth-label is-block" *ngFor="let file of availableFiles">
|
||||
{{ file.filename }} ({{ file.filesize | filesize }})
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<div class="notification is-danger is-light" *ngIf="error">{{ error }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<button class="button is-success" [disabled]="saving" [ngClass]="{ 'is-loading': saving }" (click)="ok()">
|
||||
<span>Add Torrent</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
.file-name {
|
||||
max-width: 24em;
|
||||
width: 24em;
|
||||
}
|
||||
|
||||
.scroll-container {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
overflow-y: auto;
|
||||
max-height: 603px;
|
||||
}
|
||||
|
||||
.is-fullwidth-label {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.is-block {
|
||||
display: block;
|
||||
}
|
||||
164
client/src/app/add-new-torrent/add-new-torrent.component.ts
Normal file
164
client/src/app/add-new-torrent/add-new-torrent.component.ts
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { TorrentService } from 'src/app/torrent.service';
|
||||
import { TorrentFileAvailability } from '../models/torrent.model';
|
||||
|
||||
@Component({
|
||||
selector: 'app-add-new-torrent',
|
||||
templateUrl: './add-new-torrent.component.html',
|
||||
styleUrls: ['./add-new-torrent.component.scss'],
|
||||
})
|
||||
export class AddNewTorrentComponent implements OnInit {
|
||||
public fileName: string;
|
||||
public magnetLink: string;
|
||||
private currentTorrentFile: string;
|
||||
|
||||
public category: string;
|
||||
|
||||
public downloadAction: number = 0;
|
||||
public finishedAction: number = 0;
|
||||
|
||||
public downloadMinSize: number = 0;
|
||||
|
||||
public availableFiles: TorrentFileAvailability[] = [];
|
||||
public downloadFiles: { [key: string]: boolean } = {};
|
||||
|
||||
public saving = false;
|
||||
public error: string;
|
||||
|
||||
private selectedFile: File;
|
||||
|
||||
constructor(private router: Router, private torrentService: TorrentService) {}
|
||||
|
||||
ngOnInit(): void {}
|
||||
|
||||
public pickFile(evt: Event): void {
|
||||
const files = (evt.target as HTMLInputElement).files;
|
||||
|
||||
if (files.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const file = files[0];
|
||||
|
||||
this.fileName = file.name;
|
||||
|
||||
this.selectedFile = file;
|
||||
|
||||
this.checkFiles();
|
||||
}
|
||||
|
||||
public downloadFileChecked(file: string): void {
|
||||
this.downloadFiles[file] = !this.downloadFiles[file];
|
||||
}
|
||||
|
||||
public ok(): void {
|
||||
this.saving = true;
|
||||
this.error = null;
|
||||
|
||||
let downloadManualFiles: string = null;
|
||||
|
||||
if (this.downloadAction === 2) {
|
||||
const selectedFiles = [];
|
||||
for (let filePath in this.downloadFiles) {
|
||||
if (this.downloadFiles[filePath] === true) {
|
||||
selectedFiles.push(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedFiles.length === 0) {
|
||||
this.error = 'No files have been selected to download';
|
||||
return;
|
||||
}
|
||||
|
||||
downloadManualFiles = selectedFiles.join(',');
|
||||
}
|
||||
|
||||
if (this.magnetLink) {
|
||||
this.torrentService
|
||||
.uploadMagnet(
|
||||
this.magnetLink,
|
||||
this.category,
|
||||
this.downloadAction,
|
||||
this.finishedAction,
|
||||
this.downloadMinSize,
|
||||
downloadManualFiles
|
||||
)
|
||||
.subscribe(
|
||||
() => {
|
||||
this.router.navigate(['/']);
|
||||
},
|
||||
(err) => {
|
||||
this.error = err.error;
|
||||
this.saving = false;
|
||||
}
|
||||
);
|
||||
} else if (this.selectedFile) {
|
||||
this.torrentService
|
||||
.uploadFile(
|
||||
this.selectedFile,
|
||||
this.category,
|
||||
this.downloadAction,
|
||||
this.finishedAction,
|
||||
this.downloadMinSize,
|
||||
downloadManualFiles
|
||||
)
|
||||
.subscribe(
|
||||
() => {
|
||||
this.router.navigate(['/']);
|
||||
},
|
||||
(err) => {
|
||||
this.error = err.error;
|
||||
this.saving = false;
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.error = 'No magnet or file uploaded';
|
||||
this.saving = false;
|
||||
}
|
||||
}
|
||||
|
||||
public checkFiles(): void {
|
||||
if (this.magnetLink && this.magnetLink === this.currentTorrentFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.saving = true;
|
||||
this.error = null;
|
||||
this.availableFiles = [];
|
||||
this.downloadFiles = {};
|
||||
|
||||
if (this.magnetLink) {
|
||||
this.torrentService.checkFilesMagnet(this.magnetLink).subscribe(
|
||||
(result) => {
|
||||
this.saving = false;
|
||||
this.availableFiles = result;
|
||||
this.currentTorrentFile = this.magnetLink;
|
||||
result.forEach((file) => {
|
||||
this.downloadFiles[file.filename] = true;
|
||||
});
|
||||
},
|
||||
(err) => {
|
||||
this.error = err.error;
|
||||
this.saving = false;
|
||||
}
|
||||
);
|
||||
} else if (this.selectedFile) {
|
||||
this.torrentService.checkFiles(this.selectedFile).subscribe(
|
||||
(result) => {
|
||||
this.saving = false;
|
||||
this.availableFiles = result;
|
||||
result.forEach((file) => {
|
||||
this.downloadFiles[file.filename] = true;
|
||||
});
|
||||
},
|
||||
(err) => {
|
||||
this.error = err.error;
|
||||
this.saving = false;
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.saving = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,12 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { AddNewTorrentComponent } from './add-new-torrent/add-new-torrent.component';
|
||||
import { AuthResolverService } from './auth-resolver.service';
|
||||
import { LoginComponent } from './login/login.component';
|
||||
import { MainLayoutComponent } from './main-layout/main-layout.component';
|
||||
import { SettingsComponent } from './settings/settings.component';
|
||||
import { SetupComponent } from './setup/setup.component';
|
||||
import { TorrentTableComponent } from './torrent-table/torrent-table.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
|
|
@ -20,6 +23,20 @@ const routes: Routes = [
|
|||
resolve: {
|
||||
isLoggedIn: AuthResolverService,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: TorrentTableComponent,
|
||||
},
|
||||
{
|
||||
path: 'add',
|
||||
component: AddNewTorrentComponent,
|
||||
},
|
||||
{
|
||||
path: 'settings',
|
||||
component: SettingsComponent,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { curray } from 'curray';
|
||||
import { FileSizePipe, NgxFilesizeModule } from 'ngx-filesize';
|
||||
import { AddNewTorrentComponent } from './add-new-torrent/add-new-torrent.component';
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent } from './app.component';
|
||||
import { AuthInterceptor } from './auth.interceptor';
|
||||
import { DownloadStatusPipe } from './download-status.pipe';
|
||||
import { LoginComponent } from './login/login.component';
|
||||
import { MainLayoutComponent } from './main-layout/main-layout.component';
|
||||
import { AddNewTorrentComponent } from './navbar/add-new-torrent/add-new-torrent.component';
|
||||
import { NavbarComponent } from './navbar/navbar.component';
|
||||
import { SettingsComponent } from './navbar/settings/settings.component';
|
||||
import { SettingsComponent } from './settings/settings.component';
|
||||
import { SetupComponent } from './setup/setup.component';
|
||||
import { TorrentDownloadComponent } from './torrent-download/torrent-download.component';
|
||||
import { TorrentFileComponent } from './torrent-file/torrent-file.component';
|
||||
|
|
@ -38,7 +39,7 @@ curray();
|
|||
SetupComponent,
|
||||
TorrentDownloadComponent,
|
||||
],
|
||||
imports: [BrowserModule, AppRoutingModule, FormsModule, HttpClientModule, NgxFilesizeModule],
|
||||
imports: [BrowserModule, AppRoutingModule, FormsModule, HttpClientModule, NgxFilesizeModule, FlexLayoutModule],
|
||||
providers: [FileSizePipe, { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<app-navbar></app-navbar>
|
||||
<section class="section main-list">
|
||||
<div class="container">
|
||||
<app-torrent-table></app-torrent-table>
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -35,6 +35,11 @@ export class TorrentFile {
|
|||
public download: Download;
|
||||
}
|
||||
|
||||
export class TorrentFileAvailability {
|
||||
public filename: string;
|
||||
public filesize: number;
|
||||
}
|
||||
|
||||
export enum RealDebridStatus {
|
||||
Processing = 0,
|
||||
WaitingForFileSelection = 1,
|
||||
|
|
|
|||
|
|
@ -1,77 +0,0 @@
|
|||
<div class="modal" [class.is-active]="isActive">
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">Add New Torrent</p>
|
||||
<button class="delete" aria-label="close" (click)="cancel()"></button>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
<div class="field">
|
||||
<label class="label">Magnet Link</label>
|
||||
<div class="control">
|
||||
<textarea
|
||||
class="textarea"
|
||||
placeholder="Paste your magnet link here"
|
||||
[(ngModel)]="magnetLink"
|
||||
[disabled]="saving"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="field">
|
||||
<label class="label">Torrent file</label>
|
||||
<div class="file has-name">
|
||||
<label class="file-label">
|
||||
<input class="file-input" type="file" name="resume" (change)="pickFile($event)" [disabled]="saving" />
|
||||
<span class="file-cta">
|
||||
<span class="file-icon">
|
||||
<i class="fas fa-upload"></i>
|
||||
</span>
|
||||
<span class="file-label"> Pick a torrent file... </span>
|
||||
</span>
|
||||
<span class="file-name">
|
||||
{{ fileName }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" [(ngModel)]="autoDelete" />
|
||||
Remove torrent when finished downloading on host
|
||||
</label>
|
||||
</div>
|
||||
<div class="notification is-danger is-light" *ngIf="error">Cannot add torrent: {{ error | json }}</div>
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
<button class="button is-success" [disabled]="saving" [ngClass]="{ 'is-loading': saving }" (click)="ok()">
|
||||
<span>Add Torrent</span>
|
||||
</button>
|
||||
<button class="button is-info" [disabled]="saving" [ngClass]="{ 'is-loading': saving }" (click)="checkFiles()">
|
||||
<span>Check available files</span>
|
||||
</button>
|
||||
<button class="button" (click)="cancel()" [disabled]="saving">Cancel</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal" [class.is-active]="isFileModalActive">
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-card file-modal">
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">Available Files</p>
|
||||
<button class="delete" aria-label="close" (click)="isFileModalActive = false"></button>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
<table class="table" style="width: 100%">
|
||||
<tbody>
|
||||
<tr *ngFor="let file of fileList">
|
||||
<td>{{ file }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
<footer class="modal-card-foot"></footer>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
.file-name {
|
||||
max-width: 24em;
|
||||
width: 24em;
|
||||
}
|
||||
|
||||
.file-modal {
|
||||
width: calc(75% - 40px);
|
||||
}
|
||||
|
|
@ -1,131 +0,0 @@
|
|||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { TorrentService } from 'src/app/torrent.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-add-new-torrent',
|
||||
templateUrl: './add-new-torrent.component.html',
|
||||
styleUrls: ['./add-new-torrent.component.scss'],
|
||||
})
|
||||
export class AddNewTorrentComponent implements OnInit {
|
||||
@Input()
|
||||
public get open(): boolean {
|
||||
return this.isActive;
|
||||
}
|
||||
|
||||
public set open(val: boolean) {
|
||||
this.reset();
|
||||
this.isActive = val;
|
||||
}
|
||||
|
||||
@Output()
|
||||
public openChange = new EventEmitter<boolean>();
|
||||
|
||||
public isActive = false;
|
||||
public isFileModalActive = false;
|
||||
|
||||
public fileName: string;
|
||||
public magnetLink: string;
|
||||
public autoDelete: boolean;
|
||||
|
||||
public fileList: string[];
|
||||
|
||||
public saving = false;
|
||||
public error: string;
|
||||
|
||||
private selectedFile: File;
|
||||
|
||||
constructor(private torrentService: TorrentService) {}
|
||||
|
||||
ngOnInit(): void {}
|
||||
|
||||
public reset(): void {
|
||||
this.fileName = '';
|
||||
this.magnetLink = '';
|
||||
this.autoDelete = false;
|
||||
|
||||
this.saving = false;
|
||||
this.selectedFile = null;
|
||||
this.error = null;
|
||||
}
|
||||
|
||||
public pickFile(evt: Event): void {
|
||||
const files = (evt.target as HTMLInputElement).files;
|
||||
|
||||
if (files.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const file = files[0];
|
||||
|
||||
this.fileName = file.name;
|
||||
|
||||
this.selectedFile = file;
|
||||
}
|
||||
|
||||
public ok(): void {
|
||||
this.saving = true;
|
||||
this.error = null;
|
||||
|
||||
if (this.magnetLink) {
|
||||
this.torrentService.uploadMagnet(this.magnetLink, this.autoDelete).subscribe(
|
||||
() => {
|
||||
this.cancel();
|
||||
},
|
||||
(err) => {
|
||||
this.error = err.error;
|
||||
this.saving = false;
|
||||
}
|
||||
);
|
||||
} else if (this.selectedFile) {
|
||||
this.torrentService.uploadFile(this.selectedFile, this.autoDelete).subscribe(
|
||||
() => {
|
||||
this.cancel();
|
||||
},
|
||||
(err) => {
|
||||
this.error = err.error;
|
||||
this.saving = false;
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public checkFiles(): void {
|
||||
this.saving = true;
|
||||
this.error = null;
|
||||
|
||||
if (this.magnetLink) {
|
||||
this.torrentService.checkFilesMagnet(this.magnetLink).subscribe(
|
||||
(result) => {
|
||||
this.saving = false;
|
||||
this.isFileModalActive = true;
|
||||
this.fileList = result;
|
||||
},
|
||||
(err) => {
|
||||
this.error = err.error;
|
||||
this.saving = false;
|
||||
}
|
||||
);
|
||||
} else if (this.selectedFile) {
|
||||
this.torrentService.checkFiles(this.selectedFile).subscribe(
|
||||
(result) => {
|
||||
this.saving = false;
|
||||
this.isFileModalActive = true;
|
||||
this.fileList = result;
|
||||
},
|
||||
(err) => {
|
||||
this.error = err.error;
|
||||
this.saving = false;
|
||||
}
|
||||
);
|
||||
} else {
|
||||
this.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
public cancel(): void {
|
||||
this.isActive = false;
|
||||
this.openChange.emit(this.open);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<nav class="navbar is-dark is-fixed-top" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item">
|
||||
<a class="navbar-item" routerLink="/">
|
||||
<img src="../../assets/logo.png" />
|
||||
</a>
|
||||
|
||||
|
|
@ -20,13 +20,19 @@
|
|||
|
||||
<div class="navbar-menu" [class.is-active]="showMobileMenu">
|
||||
<div class="navbar-start">
|
||||
<a class="navbar-item" (click)="this.showNewTorrent = true">
|
||||
<a class="navbar-item" routerLink="/">
|
||||
<span class="icon">
|
||||
<i class="fas fa-table" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span>Torrents</span>
|
||||
</a>
|
||||
<a class="navbar-item" routerLink="/add">
|
||||
<span class="icon">
|
||||
<i class="fas fa-plus" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span>Add New Torrent</span>
|
||||
</a>
|
||||
<a class="navbar-item" (click)="this.showSettings = true">
|
||||
<a class="navbar-item" routerLink="/settings">
|
||||
<span class="icon">
|
||||
<i class="fas fa-cog" aria-hidden="true"></i>
|
||||
</span>
|
||||
|
|
@ -55,6 +61,3 @@
|
|||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<app-add-new-torrent [(open)]="showNewTorrent"></app-add-new-torrent>
|
||||
<app-settings [(open)]="showSettings"></app-settings>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { TorrentService } from '../torrent.service';
|
||||
import { SettingsService } from '../settings.service';
|
||||
import { Profile } from '../models/profile.model';
|
||||
import { AuthService } from '../auth.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { AuthService } from '../auth.service';
|
||||
import { Profile } from '../models/profile.model';
|
||||
import { SettingsService } from '../settings.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-navbar',
|
||||
|
|
@ -13,9 +12,6 @@ import { Router } from '@angular/router';
|
|||
export class NavbarComponent implements OnInit {
|
||||
public showMobileMenu = false;
|
||||
|
||||
public showNewTorrent = false;
|
||||
public showSettings = false;
|
||||
|
||||
public profile: Profile;
|
||||
|
||||
constructor(private settingsService: SettingsService, private authService: AuthService, private router: Router) {}
|
||||
|
|
|
|||
|
|
@ -1,265 +0,0 @@
|
|||
<div class="modal" [class.is-active]="isActive">
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">Settings</p>
|
||||
<button class="delete" aria-label="close" (click)="cancel()"></button>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
<div class="tabs">
|
||||
<ul>
|
||||
<li [ngClass]="{ 'is-active': activeTab === 0 }" (click)="activeTab = 0">
|
||||
<a>General</a>
|
||||
</li>
|
||||
<li [ngClass]="{ 'is-active': activeTab === 1 }" (click)="activeTab = 1">
|
||||
<a>Download Client</a>
|
||||
</li>
|
||||
<li [ngClass]="{ 'is-active': activeTab === 2 }" (click)="activeTab = 2">
|
||||
<a>Radarr/Sonarr</a>
|
||||
</li>
|
||||
<li [ngClass]="{ 'is-active': activeTab === 3 }" (click)="activeTab = 3">
|
||||
<a>Tests</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div *ngIf="activeTab === 0">
|
||||
<div class="field">
|
||||
<label class="label">Real-Debrid API Key</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" maxlength="100" [(ngModel)]="settingRealDebridApiKey" />
|
||||
</div>
|
||||
<p class="help">
|
||||
You can find your API key here:
|
||||
<a href="https://real-debrid.com/apitoken" target="_blank" rel="noopener"
|
||||
>https://real-debrid.com/apitoken</a
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Log level</label>
|
||||
<div class="control select is-fullwidth">
|
||||
<select [(ngModel)]="settingLogLevel">
|
||||
<option value="Verbose">Verbose</option>
|
||||
<option value="Debug">Debug</option>
|
||||
<option value="Information">Information</option>
|
||||
<option value="Warning">Warning</option>
|
||||
<option value="Error">Error</option>
|
||||
<option value="Fatal">Fatal</option>
|
||||
</select>
|
||||
</div>
|
||||
<p class="help">Recommended level is Warning, set to Debug to get the most info.</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Download path</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" maxlength="1000" [(ngModel)]="settingDownloadPath" />
|
||||
</div>
|
||||
<p class="help">Path in the docker container to download files to (i.e. /data/downloads).</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Mapped path</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" maxlength="1000" [(ngModel)]="settingMappedPath" />
|
||||
</div>
|
||||
<p class="help">
|
||||
Path where files are downloaded to on your host (i.e. D:\Downloads). This path is used for Radarr and Sonarr
|
||||
to find your downloads.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Maximum unpack processes</label>
|
||||
<div class="control">
|
||||
<input class="input" type="number" max="100" min="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">
|
||||
<div class="field">
|
||||
<label class="label">Download client</label>
|
||||
<div class="control select is-fullwidth">
|
||||
<select [(ngModel)]="settingDownloadClient">
|
||||
<option value="Simple">Simple Downloader</option>
|
||||
<option value="MultiPart">Multi Part Downloader</option>
|
||||
</select>
|
||||
</div>
|
||||
<p class="help">
|
||||
Select which download client to use, see the
|
||||
<a href="https://github.com/rogerfar/rdt-client/" target="_blank">README</a> for the various options.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field" *ngIf="settingDownloadClient === 'MultiPart'">
|
||||
<label class="label">Temp Download path</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" maxlength="1000" [(ngModel)]="settingTempPath" />
|
||||
</div>
|
||||
<p class="help">
|
||||
Path in the docker container to temporarily download to (i.e. /data/temp). Make sure the docker container
|
||||
has enough disk space if using a path inside the container.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Maximum parallel downloads</label>
|
||||
<div class="control">
|
||||
<input class="input" type="number" max="100" min="1" [(ngModel)]="settingDownloadLimit" />
|
||||
</div>
|
||||
<p class="help">Maximum amount of torrents that get downloaded to your host at the same time.</p>
|
||||
</div>
|
||||
|
||||
<div class="field" *ngIf="settingDownloadClient === 'MultiPart'">
|
||||
<label class="label">Parallel connections per download</label>
|
||||
<div class="control">
|
||||
<input class="input" type="number" max="100" min="0" [(ngModel)]="settingDownloadChunkCount" />
|
||||
</div>
|
||||
<p class="help">
|
||||
Maximum amount of parallel threads that are used to download a single torrent to your host. If set to 1 no
|
||||
parallel downloading will be done.
|
||||
</p>
|
||||
</div>
|
||||
<div class="field" *ngIf="settingDownloadClient === 'MultiPart'">
|
||||
<label class="label">Download speed (in MB/s)</label>
|
||||
<div class="control">
|
||||
<input class="input" type="number" max="1000" min="0" [(ngModel)]="settingDownloadMaxSpeed" />
|
||||
</div>
|
||||
<p class="help">Maximum download speed in Megabytes per second. When set to 0 unlimited speed is used.</p>
|
||||
</div>
|
||||
<div class="field" *ngIf="settingDownloadClient === 'MultiPart'">
|
||||
<label class="label">Proxy Server</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" maxlength="1000" [(ngModel)]="settingProxyServer" />
|
||||
</div>
|
||||
<p class="help">Address of a proxy server.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="activeTab === 2">
|
||||
<div class="field">
|
||||
<h3 class="subtitle">
|
||||
The following settings only apply when a torrent gets added through the qbittorrent API, usually Radarr or
|
||||
Sonarr.
|
||||
</h3>
|
||||
<label class="label">Minimum file size to download</label>
|
||||
<div class="control">
|
||||
<div class="field has-addons" style="margin-bottom: 0">
|
||||
<div class="control is-expanded">
|
||||
<input class="input" type="number" max="100" min="1" [(ngModel)]="settingMinFileSize" />
|
||||
</div>
|
||||
<div class="control">
|
||||
<a class="button is-static">MB</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="help">
|
||||
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 RealDebrid having to re-download the torrent.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" [(ngModel)]="settingOnlyDownloadAvailableFiles" />
|
||||
Only download available files on RealDebrid
|
||||
</label>
|
||||
<div class="help">
|
||||
When selected, it will only download files in the torrent that have been download by Real Debrid. You can
|
||||
use this in combination with the Min File size setting above.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="activeTab === 3">
|
||||
<div class="field">
|
||||
<label class="label">Test download path permissions</label>
|
||||
<div class="control">
|
||||
<button
|
||||
class="button is-warning"
|
||||
(click)="testDownloadPath()"
|
||||
[disabled]="saving"
|
||||
[ngClass]="{ 'is-loading': saving }"
|
||||
*ngIf="!testPathError && !testPathSuccess"
|
||||
>
|
||||
Test permissions
|
||||
</button>
|
||||
<div class="notification is-danger is-light" *ngIf="testPathError">
|
||||
Could not test your download path<br />
|
||||
{{ testPathError }}
|
||||
</div>
|
||||
|
||||
<div class="notification is-success is-light" *ngIf="testPathSuccess">Your download path looks good!</div>
|
||||
</div>
|
||||
<div class="help">This will check if the download folder has write permissions.</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Test RealDebrid download speed</label>
|
||||
<div class="control">
|
||||
<button
|
||||
class="button is-warning"
|
||||
(click)="testDownloadSpeed()"
|
||||
[disabled]="saving"
|
||||
[ngClass]="{ 'is-loading': saving }"
|
||||
*ngIf="!testDownloadSpeedError && !testDownloadSpeedSuccess"
|
||||
>
|
||||
Test download speed
|
||||
</button>
|
||||
<div class="notification is-danger is-light" *ngIf="testDownloadSpeedError">
|
||||
Could not test your download speed<br />
|
||||
{{ testDownloadSpeedError }}
|
||||
</div>
|
||||
|
||||
<div class="notification is-success is-light" *ngIf="testDownloadSpeedSuccess">
|
||||
Download speed {{ testDownloadSpeedSuccess | filesize }}/s
|
||||
</div>
|
||||
</div>
|
||||
<div class="help">
|
||||
This will attempt to download a 10GB file from Real Debrid. When 50MB has been downloaded the test will
|
||||
stop.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Test download folder write speed</label>
|
||||
<div class="control">
|
||||
<button
|
||||
class="button is-warning"
|
||||
(click)="testWriteSpeed()"
|
||||
[disabled]="saving"
|
||||
[ngClass]="{ 'is-loading': saving }"
|
||||
*ngIf="!testWriteSpeedError && !testWriteSpeedSuccess"
|
||||
>
|
||||
Test write speed
|
||||
</button>
|
||||
<div class="notification is-danger is-light" *ngIf="testWriteSpeedError">
|
||||
Could not test your download speed<br />
|
||||
{{ testWriteSpeedError }}
|
||||
</div>
|
||||
|
||||
<div class="notification is-success is-light" *ngIf="testWriteSpeedSuccess">
|
||||
Write speed {{ testWriteSpeedSuccess | filesize }}/s
|
||||
</div>
|
||||
</div>
|
||||
<div class="help">
|
||||
This will write a small file to your download folder to see how fast it can write to it.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="notification is-danger is-light" *ngIf="error?.length > 0">Error saving settings: {{ error }}</div>
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
<button class="button is-success" (click)="ok()" [disabled]="saving" [ngClass]="{ 'is-loading': saving }">
|
||||
Save Settings
|
||||
</button>
|
||||
<button class="button" (click)="cancel()" [disabled]="saving" [ngClass]="{ 'is-loading': saving }">Cancel</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
254
client/src/app/settings/settings.component.html
Normal file
254
client/src/app/settings/settings.component.html
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
<div class="tabs">
|
||||
<ul>
|
||||
<li [ngClass]="{ 'is-active': activeTab === 0 }" (click)="activeTab = 0">
|
||||
<a>General</a>
|
||||
</li>
|
||||
<li [ngClass]="{ 'is-active': activeTab === 1 }" (click)="activeTab = 1">
|
||||
<a>Download Client</a>
|
||||
</li>
|
||||
<li [ngClass]="{ 'is-active': activeTab === 2 }" (click)="activeTab = 2">
|
||||
<a>Radarr/Sonarr</a>
|
||||
</li>
|
||||
<li [ngClass]="{ 'is-active': activeTab === 3 }" (click)="activeTab = 3">
|
||||
<a>Tests</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div *ngIf="activeTab === 0">
|
||||
<div class="field">
|
||||
<label class="label">Real-Debrid API Key</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" maxlength="100" [(ngModel)]="settingRealDebridApiKey" />
|
||||
</div>
|
||||
<p class="help">
|
||||
You can find your API key here:
|
||||
<a href="https://real-debrid.com/apitoken" target="_blank" rel="noopener">https://real-debrid.com/apitoken</a>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Log level</label>
|
||||
<div class="control select is-fullwidth">
|
||||
<select [(ngModel)]="settingLogLevel">
|
||||
<option value="Verbose">Verbose</option>
|
||||
<option value="Debug">Debug</option>
|
||||
<option value="Information">Information</option>
|
||||
<option value="Warning">Warning</option>
|
||||
<option value="Error">Error</option>
|
||||
<option value="Fatal">Fatal</option>
|
||||
</select>
|
||||
</div>
|
||||
<p class="help">Recommended level is Warning, set to Debug to get the most info.</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Download path</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" maxlength="1000" [(ngModel)]="settingDownloadPath" />
|
||||
</div>
|
||||
<p class="help">Path in the docker container to download files to (i.e. /data/downloads).</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Mapped path</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" maxlength="1000" [(ngModel)]="settingMappedPath" />
|
||||
</div>
|
||||
<p class="help">
|
||||
Path where files are downloaded to on your host (i.e. D:\Downloads). This path is used for Radarr and Sonarr to
|
||||
find your downloads.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Maximum unpack processes</label>
|
||||
<div class="control">
|
||||
<input class="input" type="number" max="100" min="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">
|
||||
<div class="field">
|
||||
<label class="label">Download client</label>
|
||||
<div class="control select is-fullwidth">
|
||||
<select [(ngModel)]="settingDownloadClient">
|
||||
<option value="Simple">Simple Downloader</option>
|
||||
<option value="MultiPart">Multi Part Downloader</option>
|
||||
</select>
|
||||
</div>
|
||||
<p class="help">
|
||||
Select which download client to use, see the
|
||||
<a href="https://github.com/rogerfar/rdt-client/" target="_blank">README</a> for the various options.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field" *ngIf="settingDownloadClient === 'MultiPart'">
|
||||
<label class="label">Temp Download path</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" maxlength="1000" [(ngModel)]="settingTempPath" />
|
||||
</div>
|
||||
<p class="help">
|
||||
Path in the docker container to temporarily download to (i.e. /data/temp). Make sure the docker container has
|
||||
enough disk space if using a path inside the container.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Maximum parallel downloads</label>
|
||||
<div class="control">
|
||||
<input class="input" type="number" max="100" min="1" [(ngModel)]="settingDownloadLimit" />
|
||||
</div>
|
||||
<p class="help">Maximum amount of torrents that get downloaded to your host at the same time.</p>
|
||||
</div>
|
||||
|
||||
<div class="field" *ngIf="settingDownloadClient === 'MultiPart'">
|
||||
<label class="label">Parallel connections per download</label>
|
||||
<div class="control">
|
||||
<input class="input" type="number" max="100" min="0" [(ngModel)]="settingDownloadChunkCount" />
|
||||
</div>
|
||||
<p class="help">
|
||||
Maximum amount of parallel threads that are used to download a single torrent to your host. If set to 1 no
|
||||
parallel downloading will be done.
|
||||
</p>
|
||||
</div>
|
||||
<div class="field" *ngIf="settingDownloadClient === 'MultiPart'">
|
||||
<label class="label">Download speed (in MB/s)</label>
|
||||
<div class="control">
|
||||
<input class="input" type="number" max="1000" min="0" [(ngModel)]="settingDownloadMaxSpeed" />
|
||||
</div>
|
||||
<p class="help">Maximum download speed in Megabytes per second. When set to 0 unlimited speed is used.</p>
|
||||
</div>
|
||||
<div class="field" *ngIf="settingDownloadClient === 'MultiPart'">
|
||||
<label class="label">Proxy Server</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" maxlength="1000" [(ngModel)]="settingProxyServer" />
|
||||
</div>
|
||||
<p class="help">Address of a proxy server.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="activeTab === 2">
|
||||
<h3 class="subtitle">
|
||||
The following settings only apply when a torrent gets added through the qbittorrent API, usually Radarr or Sonarr.
|
||||
</h3>
|
||||
<div class="field">
|
||||
<label class="label">Minimum file size to download</label>
|
||||
<div class="control">
|
||||
<div class="field has-addons" style="margin-bottom: 0">
|
||||
<div class="control is-expanded">
|
||||
<input class="input" type="number" max="1000" min="0" [(ngModel)]="settingMinFileSize" />
|
||||
</div>
|
||||
<div class="control">
|
||||
<a class="button is-static">MB</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="help">
|
||||
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
|
||||
RealDebrid having to re-download the torrent.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" [(ngModel)]="settingOnlyDownloadAvailableFiles" />
|
||||
Only download available files on RealDebrid
|
||||
</label>
|
||||
<div class="help">
|
||||
When selected, it will only download files in the torrent that have been download by Real Debrid. You can use
|
||||
this in combination with the Min File size setting above.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="activeTab === 3">
|
||||
<div class="field">
|
||||
<label class="label">Test download path permissions</label>
|
||||
<div class="control">
|
||||
<button
|
||||
class="button is-warning"
|
||||
(click)="testDownloadPath()"
|
||||
[disabled]="saving"
|
||||
[ngClass]="{ 'is-loading': saving }"
|
||||
*ngIf="!testPathError && !testPathSuccess"
|
||||
>
|
||||
Test permissions
|
||||
</button>
|
||||
<div class="notification is-danger is-light" *ngIf="testPathError">
|
||||
Could not test your download path<br />
|
||||
{{ testPathError }}
|
||||
</div>
|
||||
|
||||
<div class="notification is-success is-light" *ngIf="testPathSuccess">Your download path looks good!</div>
|
||||
</div>
|
||||
<div class="help">This will check if the download folder has write permissions.</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Test RealDebrid download speed</label>
|
||||
<div class="control">
|
||||
<button
|
||||
class="button is-warning"
|
||||
(click)="testDownloadSpeed()"
|
||||
[disabled]="saving"
|
||||
[ngClass]="{ 'is-loading': saving }"
|
||||
*ngIf="!testDownloadSpeedError && !testDownloadSpeedSuccess"
|
||||
>
|
||||
Test download speed
|
||||
</button>
|
||||
<div class="notification is-danger is-light" *ngIf="testDownloadSpeedError">
|
||||
Could not test your download speed<br />
|
||||
{{ testDownloadSpeedError }}
|
||||
</div>
|
||||
|
||||
<div class="notification is-success is-light" *ngIf="testDownloadSpeedSuccess">
|
||||
Download speed {{ testDownloadSpeedSuccess | filesize }}/s
|
||||
</div>
|
||||
</div>
|
||||
<div class="help">
|
||||
This will attempt to download a 10GB file from Real Debrid. When 50MB has been downloaded the test will stop.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Test download folder write speed</label>
|
||||
<div class="control">
|
||||
<button
|
||||
class="button is-warning"
|
||||
(click)="testWriteSpeed()"
|
||||
[disabled]="saving"
|
||||
[ngClass]="{ 'is-loading': saving }"
|
||||
*ngIf="!testWriteSpeedError && !testWriteSpeedSuccess"
|
||||
>
|
||||
Test write speed
|
||||
</button>
|
||||
<div class="notification is-danger is-light" *ngIf="testWriteSpeedError">
|
||||
Could not test your download speed<br />
|
||||
{{ testWriteSpeedError }}
|
||||
</div>
|
||||
|
||||
<div class="notification is-success is-light" *ngIf="testWriteSpeedSuccess">
|
||||
Write speed {{ testWriteSpeedSuccess | filesize }}/s
|
||||
</div>
|
||||
</div>
|
||||
<div class="help">This will write a small file to your download folder to see how fast it can write to it.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<div class="notification is-danger is-light" *ngIf="error?.length > 0">Error saving settings: {{ error }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field" *ngIf="activeTab != 3">
|
||||
<div class="control">
|
||||
<button class="button is-success" (click)="ok()" [disabled]="saving" [ngClass]="{ 'is-loading': saving }">
|
||||
Save Settings
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Setting } from 'src/app/models/setting.model';
|
||||
import { SettingsService } from 'src/app/settings.service';
|
||||
|
||||
|
|
@ -8,23 +8,8 @@ import { SettingsService } from 'src/app/settings.service';
|
|||
styleUrls: ['./settings.component.scss'],
|
||||
})
|
||||
export class SettingsComponent implements OnInit {
|
||||
@Input()
|
||||
public get open(): boolean {
|
||||
return this.isActive;
|
||||
}
|
||||
|
||||
public set open(val: boolean) {
|
||||
this.reset();
|
||||
this.isActive = val;
|
||||
}
|
||||
|
||||
@Output()
|
||||
public openChange = new EventEmitter<boolean>();
|
||||
|
||||
public activeTab = 0;
|
||||
|
||||
public isActive = false;
|
||||
|
||||
public saving = false;
|
||||
public error: string;
|
||||
|
||||
|
|
@ -53,7 +38,9 @@ export class SettingsComponent implements OnInit {
|
|||
|
||||
constructor(private settingsService: SettingsService) {}
|
||||
|
||||
ngOnInit(): void {}
|
||||
ngOnInit(): void {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
this.saving = false;
|
||||
|
|
@ -142,10 +129,12 @@ export class SettingsComponent implements OnInit {
|
|||
|
||||
this.settingsService.update(settings).subscribe(
|
||||
() => {
|
||||
this.isActive = false;
|
||||
this.openChange.emit(this.open);
|
||||
setTimeout(() => {
|
||||
this.saving = false;
|
||||
}, 1000);
|
||||
},
|
||||
(err) => {
|
||||
this.saving = false;
|
||||
this.error = err;
|
||||
}
|
||||
);
|
||||
|
|
@ -201,11 +190,6 @@ export class SettingsComponent implements OnInit {
|
|||
);
|
||||
}
|
||||
|
||||
public cancel(): void {
|
||||
this.isActive = false;
|
||||
this.openChange.emit(this.open);
|
||||
}
|
||||
|
||||
private getSetting(settings: Setting[], key: string): string {
|
||||
const setting = settings.filter((m) => m.settingId === key);
|
||||
|
||||
|
|
@ -2,7 +2,7 @@ import { HttpClient } from '@angular/common/http';
|
|||
import { Injectable } from '@angular/core';
|
||||
import * as signalR from '@microsoft/signalr';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
import { Torrent } from './models/torrent.model';
|
||||
import { Torrent, TorrentFileAvailability } from './models/torrent.model';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
|
|
@ -31,30 +31,51 @@ export class TorrentService {
|
|||
return this.http.get<Torrent[]>(`/Api/Torrents`);
|
||||
}
|
||||
|
||||
public uploadMagnet(magnetLink: string, autoDelete: boolean): Observable<void> {
|
||||
public uploadMagnet(
|
||||
magnetLink: string,
|
||||
category: string,
|
||||
downloadAction: number,
|
||||
finishedAction: number,
|
||||
downloadMinSize: number,
|
||||
downloadManualFiles: string
|
||||
): Observable<void> {
|
||||
return this.http.post<void>(`/Api/Torrents/UploadMagnet`, {
|
||||
magnetLink,
|
||||
autoDelete,
|
||||
category,
|
||||
downloadAction,
|
||||
finishedAction,
|
||||
downloadMinSize,
|
||||
downloadManualFiles,
|
||||
});
|
||||
}
|
||||
|
||||
public uploadFile(file: File, autoDelete: boolean): Observable<void> {
|
||||
public uploadFile(
|
||||
file: File,
|
||||
category: string,
|
||||
downloadAction: number,
|
||||
finishedAction: number,
|
||||
downloadMinSize: number,
|
||||
downloadManualFiles: string
|
||||
): Observable<void> {
|
||||
const formData: FormData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('formData', JSON.stringify({ autoDelete }));
|
||||
formData.append(
|
||||
'formData',
|
||||
JSON.stringify({ category, downloadAction, finishedAction, downloadMinSize, downloadManualFiles })
|
||||
);
|
||||
return this.http.post<void>(`/Api/Torrents/UploadFile`, formData);
|
||||
}
|
||||
|
||||
public checkFilesMagnet(magnetLink: string): Observable<string[]> {
|
||||
return this.http.post<string[]>(`/Api/Torrents/CheckFilesMagnet`, {
|
||||
public checkFilesMagnet(magnetLink: string): Observable<TorrentFileAvailability[]> {
|
||||
return this.http.post<TorrentFileAvailability[]>(`/Api/Torrents/CheckFilesMagnet`, {
|
||||
magnetLink,
|
||||
});
|
||||
}
|
||||
|
||||
public checkFiles(file: File): Observable<string[]> {
|
||||
public checkFiles(file: File): Observable<TorrentFileAvailability[]> {
|
||||
const formData: FormData = new FormData();
|
||||
formData.append('file', file);
|
||||
return this.http.post<string[]>(`/Api/Torrents/CheckFiles`, formData);
|
||||
return this.http.post<TorrentFileAvailability[]>(`/Api/Torrents/CheckFiles`, formData);
|
||||
}
|
||||
|
||||
public delete(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System.Linq;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RdtClient.Data.Enums;
|
||||
using RdtClient.Data.Models.Data;
|
||||
|
||||
namespace RdtClient.Data.Data
|
||||
|
|
@ -13,10 +14,21 @@ namespace RdtClient.Data.Data
|
|||
Task<IList<Torrent>> Get();
|
||||
Task<Torrent> GetById(Guid torrentId);
|
||||
Task<Torrent> GetByHash(String hash);
|
||||
Task<Torrent> Add(String realDebridId, String hash, String category, Boolean autoDelete, String fileOrMagnetContents, Boolean isFile);
|
||||
|
||||
Task<Torrent> Add(String realDebridId,
|
||||
String hash,
|
||||
String category,
|
||||
TorrentDownloadAction downloadAction,
|
||||
TorrentFinishedAction finishedAction,
|
||||
Int32 downloadMinSize,
|
||||
String downloadManualFiles,
|
||||
String fileOrMagnetContents,
|
||||
Boolean isFile);
|
||||
|
||||
Task UpdateRdData(Torrent torrent);
|
||||
Task UpdateCategory(Guid torrentId, String category);
|
||||
Task UpdateComplete(Guid torrentId, DateTimeOffset? datetime);
|
||||
Task UpdateFilesSelected(Guid torrentId, DateTimeOffset datetime);
|
||||
Task Delete(Guid torrentId);
|
||||
Task VoidCache();
|
||||
}
|
||||
|
|
@ -95,7 +107,15 @@ namespace RdtClient.Data.Data
|
|||
return dbTorrent;
|
||||
}
|
||||
|
||||
public async Task<Torrent> Add(String realDebridId, String hash, String category, Boolean autoDelete, String fileOrMagnetContents, Boolean isFile)
|
||||
public async Task<Torrent> Add(String realDebridId,
|
||||
String hash,
|
||||
String category,
|
||||
TorrentDownloadAction downloadAction,
|
||||
TorrentFinishedAction finishedAction,
|
||||
Int32 downloadMinSize,
|
||||
String downloadManualFiles,
|
||||
String fileOrMagnetContents,
|
||||
Boolean isFile)
|
||||
{
|
||||
var torrent = new Torrent
|
||||
{
|
||||
|
|
@ -104,7 +124,10 @@ namespace RdtClient.Data.Data
|
|||
RdId = realDebridId,
|
||||
Hash = hash.ToLower(),
|
||||
Category = category,
|
||||
AutoDelete = autoDelete,
|
||||
DownloadAction = downloadAction,
|
||||
FinishedAction = finishedAction,
|
||||
DownloadMinSize = downloadMinSize,
|
||||
DownloadManualFiles = downloadManualFiles,
|
||||
FileOrMagnet = fileOrMagnetContents,
|
||||
IsFile = isFile
|
||||
};
|
||||
|
|
@ -148,7 +171,7 @@ namespace RdtClient.Data.Data
|
|||
|
||||
await VoidCache();
|
||||
}
|
||||
|
||||
|
||||
public async Task UpdateCategory(Guid torrentId, String category)
|
||||
{
|
||||
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
|
||||
|
|
@ -161,7 +184,7 @@ namespace RdtClient.Data.Data
|
|||
dbTorrent.Category = category;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
|
||||
await VoidCache();
|
||||
}
|
||||
|
||||
|
|
@ -172,7 +195,18 @@ namespace RdtClient.Data.Data
|
|||
dbTorrent.Completed = datetime;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
|
||||
await VoidCache();
|
||||
}
|
||||
|
||||
public async Task UpdateFilesSelected(Guid torrentId, DateTimeOffset datetime)
|
||||
{
|
||||
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
|
||||
|
||||
dbTorrent.FilesSelected = datetime;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await VoidCache();
|
||||
}
|
||||
|
||||
|
|
@ -188,7 +222,7 @@ namespace RdtClient.Data.Data
|
|||
_dataContext.Torrents.Remove(dbTorrent);
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
|
||||
await VoidCache();
|
||||
}
|
||||
|
||||
|
|
@ -206,4 +240,4 @@ namespace RdtClient.Data.Data
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
9
server/RdtClient.Data/Enums/TorrentDownloadAction.cs
Normal file
9
server/RdtClient.Data/Enums/TorrentDownloadAction.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
namespace RdtClient.Data.Enums
|
||||
{
|
||||
public enum TorrentDownloadAction
|
||||
{
|
||||
DownloadAll = 0,
|
||||
DownloadAvailableFiles = 1,
|
||||
DownloadManual = 2
|
||||
}
|
||||
}
|
||||
9
server/RdtClient.Data/Enums/TorrentFinishedAction.cs
Normal file
9
server/RdtClient.Data/Enums/TorrentFinishedAction.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
namespace RdtClient.Data.Enums
|
||||
{
|
||||
public enum TorrentFinishedAction
|
||||
{
|
||||
None = 0,
|
||||
RemoveAllTorrents = 1,
|
||||
RemoveRealDebrid = 2
|
||||
}
|
||||
}
|
||||
430
server/RdtClient.Data/Migrations/20210718222602_Torrents_Add_Actions.Designer.cs
generated
Normal file
430
server/RdtClient.Data/Migrations/20210718222602_Torrents_Add_Actions.Designer.cs
generated
Normal file
|
|
@ -0,0 +1,430 @@
|
|||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using RdtClient.Data.Data;
|
||||
|
||||
namespace RdtClient.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(DataContext))]
|
||||
[Migration("20210718222602_Torrents_Add_Actions")]
|
||||
partial class Torrents_Add_Actions
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "5.0.8");
|
||||
|
||||
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");
|
||||
});
|
||||
|
||||
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");
|
||||
});
|
||||
|
||||
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");
|
||||
});
|
||||
|
||||
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");
|
||||
});
|
||||
|
||||
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");
|
||||
});
|
||||
|
||||
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");
|
||||
});
|
||||
|
||||
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");
|
||||
});
|
||||
|
||||
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<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>("DownloadAction")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("DownloadManualFiles")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("DownloadMinSize")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("FileOrMagnet")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("FinishedAction")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Hash")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsFile")
|
||||
.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.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,67 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace RdtClient.Data.Migrations
|
||||
{
|
||||
public partial class Torrents_Add_Actions : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "AutoDelete",
|
||||
table: "Torrents");
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "FinishedAction",
|
||||
table: "Torrents",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "DownloadAction",
|
||||
table: "Torrents",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "DownloadManualFiles",
|
||||
table: "Torrents",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "DownloadMinSize",
|
||||
table: "Torrents",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DownloadAction",
|
||||
table: "Torrents");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DownloadManualFiles",
|
||||
table: "Torrents");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DownloadMinSize",
|
||||
table: "Torrents");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "FinishedAction",
|
||||
table: "Torrents");
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "AutoDelete",
|
||||
table: "Torrents",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
433
server/RdtClient.Data/Migrations/20210718231758_Torrents_Add_FilesSelected.Designer.cs
generated
Normal file
433
server/RdtClient.Data/Migrations/20210718231758_Torrents_Add_FilesSelected.Designer.cs
generated
Normal file
|
|
@ -0,0 +1,433 @@
|
|||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using RdtClient.Data.Data;
|
||||
|
||||
namespace RdtClient.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(DataContext))]
|
||||
[Migration("20210718231758_Torrents_Add_FilesSelected")]
|
||||
partial class Torrents_Add_FilesSelected
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "5.0.8");
|
||||
|
||||
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");
|
||||
});
|
||||
|
||||
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");
|
||||
});
|
||||
|
||||
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");
|
||||
});
|
||||
|
||||
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");
|
||||
});
|
||||
|
||||
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");
|
||||
});
|
||||
|
||||
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");
|
||||
});
|
||||
|
||||
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");
|
||||
});
|
||||
|
||||
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<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>("DownloadAction")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("DownloadManualFiles")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("DownloadMinSize")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
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<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.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,24 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace RdtClient.Data.Migrations
|
||||
{
|
||||
public partial class Torrents_Add_FilesSelected : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTimeOffset>(
|
||||
name: "FilesSelected",
|
||||
table: "Torrents",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "FilesSelected",
|
||||
table: "Torrents");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ namespace RdtClient.Data.Migrations
|
|||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "5.0.3");
|
||||
.HasAnnotation("ProductVersion", "5.0.8");
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
|
|
@ -285,18 +285,30 @@ namespace RdtClient.Data.Migrations
|
|||
b.Property<DateTimeOffset>("Added")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("AutoDelete")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Category")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset?>("Completed")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("DownloadAction")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("DownloadManualFiles")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("DownloadMinSize")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("FileOrMagnet")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset?>("FilesSelected")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("FinishedAction")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Hash")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
|
|
|
|||
|
|
@ -17,17 +17,15 @@ namespace RdtClient.Data.Models.Data
|
|||
|
||||
public String Category { get; set; }
|
||||
|
||||
public TorrentDownloadAction DownloadAction { get; set; }
|
||||
public TorrentFinishedAction FinishedAction { get; set; }
|
||||
public Int32 DownloadMinSize { get; set; }
|
||||
public String DownloadManualFiles { get; set; }
|
||||
|
||||
public DateTimeOffset Added { get; set; }
|
||||
public DateTimeOffset? FilesSelected { get; set; }
|
||||
public DateTimeOffset? Completed { get; set; }
|
||||
|
||||
public Boolean DownloadOnlyAvailableFiles { get; set; }
|
||||
|
||||
public String DownloadFiles { get; set; }
|
||||
|
||||
public Int32 MinimumFileSize { get; set; }
|
||||
|
||||
public Boolean AutoDelete { get; set; }
|
||||
|
||||
public String FileOrMagnet { get; set; }
|
||||
public Boolean IsFile { get; set; }
|
||||
|
||||
|
|
@ -68,5 +66,19 @@ namespace RdtClient.Data.Models.Data
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
[NotMapped]
|
||||
public IList<String> ManualFiles
|
||||
{
|
||||
get
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(DownloadManualFiles))
|
||||
{
|
||||
return new List<String>();
|
||||
}
|
||||
|
||||
return DownloadManualFiles.Split(",");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ namespace RdtClient.Service.Services
|
|||
Task<IList<TorrentFileItem>> TorrentFileContents(String hash);
|
||||
Task<TorrentProperties> TorrentProperties(String hash);
|
||||
Task TorrentsDelete(String hash, Boolean deleteFiles);
|
||||
Task TorrentsAddMagnet(String magnetLink, String category, Boolean autoDelete);
|
||||
Task TorrentsAddFile(Byte[] fileBytes, String category, Boolean autoDelete);
|
||||
Task TorrentsAddMagnet(String magnetLink, String category);
|
||||
Task TorrentsAddFile(Byte[] fileBytes, String category);
|
||||
Task TorrentsSetCategory(String hash, String category);
|
||||
Task<IDictionary<String, TorrentCategory>> TorrentsCategories();
|
||||
Task CategoryCreate(String category);
|
||||
|
|
@ -432,14 +432,24 @@ namespace RdtClient.Service.Services
|
|||
await _torrents.Delete(torrent.TorrentId, true, true, deleteFiles);
|
||||
}
|
||||
|
||||
public async Task TorrentsAddMagnet(String magnetLink, String category, Boolean autoDelete)
|
||||
public async Task TorrentsAddMagnet(String magnetLink, String category)
|
||||
{
|
||||
await _torrents.UploadMagnet(magnetLink, category, autoDelete);
|
||||
var onlyDownloadAvailableFilesSetting = await _settings.GetNumber("OnlyDownloadAvailableFiles");
|
||||
var minFileSizeSetting = await _settings.GetNumber("MinFileSize");
|
||||
|
||||
var downloadAction = onlyDownloadAvailableFilesSetting == 1 ? TorrentDownloadAction.DownloadAvailableFiles : TorrentDownloadAction.DownloadAll;
|
||||
|
||||
await _torrents.UploadMagnet(magnetLink, category, downloadAction, TorrentFinishedAction.None, minFileSizeSetting, null);
|
||||
}
|
||||
|
||||
public async Task TorrentsAddFile(Byte[] fileBytes, String category, Boolean autoDelete)
|
||||
public async Task TorrentsAddFile(Byte[] fileBytes, String category)
|
||||
{
|
||||
await _torrents.UploadFile(fileBytes, category, autoDelete);
|
||||
var onlyDownloadAvailableFilesSetting = await _settings.GetNumber("OnlyDownloadAvailableFiles");
|
||||
var minFileSizeSetting = await _settings.GetNumber("MinFileSize");
|
||||
|
||||
var downloadAction = onlyDownloadAvailableFilesSetting == 1 ? TorrentDownloadAction.DownloadAvailableFiles : TorrentDownloadAction.DownloadAll;
|
||||
|
||||
await _torrents.UploadFile(fileBytes, category, downloadAction, TorrentFinishedAction.None, minFileSizeSetting, null);
|
||||
}
|
||||
|
||||
public async Task TorrentsSetCategory(String hash, String category)
|
||||
|
|
|
|||
|
|
@ -80,18 +80,7 @@ namespace RdtClient.Service.Services
|
|||
Log.Debug($"No RealDebridApiKey set!");
|
||||
return;
|
||||
}
|
||||
|
||||
var settingMinFileSize = settings.GetNumber("MinFileSize");
|
||||
if (settingMinFileSize <= 0)
|
||||
{
|
||||
settingMinFileSize = 0;
|
||||
}
|
||||
|
||||
settingMinFileSize = settingMinFileSize * 1024 * 1024;
|
||||
|
||||
var settingOnlyDownloadAvailableFilesRaw = settings.GetNumber("OnlyDownloadAvailableFiles");
|
||||
var settingOnlyDownloadAvailableFiles = settingOnlyDownloadAvailableFilesRaw == 1;
|
||||
|
||||
|
||||
var settingDownloadLimit = settings.GetNumber("DownloadLimit");
|
||||
if (settingDownloadLimit < 1)
|
||||
{
|
||||
|
|
@ -365,35 +354,52 @@ namespace RdtClient.Service.Services
|
|||
continue;
|
||||
}
|
||||
|
||||
// The files are selected but there are no downloads yet, check if Real Debrid has generated links yet.
|
||||
if (torrent.Downloads.Count == 0 && torrent.FilesSelected != null)
|
||||
{
|
||||
await _torrents.CheckForLinks(torrent.TorrentId);
|
||||
}
|
||||
|
||||
// RealDebrid is waiting for file selection, select which files to download.
|
||||
if ((torrent.RdStatus == RealDebridStatus.WaitingForFileSelection || torrent.RdStatus == RealDebridStatus.Finished) &&
|
||||
torrent.Downloads.Count == 0)
|
||||
torrent.Downloads.Count == 0 &&
|
||||
torrent.FilesSelected == null)
|
||||
{
|
||||
Log.Debug($"Torrent {torrent.RdId} selecting files");
|
||||
|
||||
var files = torrent.Files;
|
||||
|
||||
if (settingOnlyDownloadAvailableFiles)
|
||||
if (torrent.DownloadAction == TorrentDownloadAction.DownloadAvailableFiles)
|
||||
{
|
||||
Log.Debug($"Torrent {torrent.RdId} determining which files are already available");
|
||||
|
||||
var availableFiles = await _torrents.GetAvailableFiles(torrent.Hash);
|
||||
|
||||
Log.Debug($"Found {availableFiles.Count} available files for torrent {torrent.RdId}");
|
||||
Log.Debug($"Found {files.Count}/{torrent.Files.Count} available files for torrent {torrent.RdId}");
|
||||
|
||||
if (availableFiles.Count > 0)
|
||||
{
|
||||
files = torrent.Files.Where(m => availableFiles.Any(f => m.Path.EndsWith(f))).ToList();
|
||||
|
||||
Log.Debug($"Selecting {files.Count} available files for torrent {torrent.RdId}");
|
||||
}
|
||||
files = torrent.Files.Where(m => availableFiles.Any(f => m.Path.EndsWith(f.Filename))).ToList();
|
||||
}
|
||||
else if (torrent.DownloadAction == TorrentDownloadAction.DownloadAll)
|
||||
{
|
||||
Log.Debug("Selecting all files");
|
||||
files = torrent.Files.ToList();
|
||||
}
|
||||
else if (torrent.DownloadAction == TorrentDownloadAction.DownloadManual)
|
||||
{
|
||||
Log.Debug("Selecting manual selected files");
|
||||
files = torrent.Files.Where(m => torrent.ManualFiles.Any(f => m.Path.EndsWith(f))).ToList();
|
||||
}
|
||||
|
||||
if (settingMinFileSize > 0)
|
||||
{
|
||||
Log.Debug($"Torrent {torrent.RdId} determining which files are over {settingMinFileSize} bytes");
|
||||
Log.Debug($"Selecting {files.Count}/{torrent.Files.Count} files for torrent {torrent.RdId}");
|
||||
|
||||
files = files.Where(m => m.Bytes > settingMinFileSize).ToList();
|
||||
if (torrent.DownloadAction != TorrentDownloadAction.DownloadManual && torrent.DownloadMinSize > 0)
|
||||
{
|
||||
var minFileSize = torrent.DownloadMinSize * 1024 * 1024;
|
||||
|
||||
Log.Debug($"Torrent {torrent.RdId} determining which files are over {minFileSize} bytes");
|
||||
|
||||
files = files.Where(m => m.Bytes > minFileSize)
|
||||
.ToList();
|
||||
|
||||
Log.Debug($"Found {files.Count} files that match the minimum file size criterea for torrent {torrent.RdId}");
|
||||
}
|
||||
|
|
@ -410,13 +416,8 @@ namespace RdtClient.Service.Services
|
|||
Log.Debug($"Selecting files for torrent {torrent.RdId}: {String.Join(", ", fileIds)}");
|
||||
|
||||
await _torrents.SelectFiles(torrent.TorrentId, fileIds);
|
||||
}
|
||||
|
||||
// If the torrent doesn't have any files at this point, don't process it further.
|
||||
if (torrent.Files.Count == 0)
|
||||
{
|
||||
Log.Debug($"No files found for torrent {torrent.RdId}!");
|
||||
continue;
|
||||
await _torrents.UpdateFilesSelected(torrent.TorrentId, DateTime.UtcNow);
|
||||
}
|
||||
|
||||
// RealDebrid finished downloading the torrent, process the file to host.
|
||||
|
|
@ -479,12 +480,19 @@ namespace RdtClient.Service.Services
|
|||
|
||||
await _torrents.UpdateComplete(torrent.TorrentId, DateTimeOffset.UtcNow);
|
||||
|
||||
// Remove the torrent from RealDebrid
|
||||
if (torrent.AutoDelete)
|
||||
switch (torrent.FinishedAction)
|
||||
{
|
||||
Log.Debug($"Torrent {torrent.RdId} removing");
|
||||
|
||||
await _torrents.Delete(torrent.TorrentId, true, true, false);
|
||||
case TorrentFinishedAction.RemoveAllTorrents:
|
||||
Log.Debug($"Torrent {torrent.RdId} removing torrents from Real Debrid and Real Debrid Client, no files");
|
||||
await _torrents.Delete(torrent.TorrentId, true, true, false);
|
||||
break;
|
||||
case TorrentFinishedAction.RemoveRealDebrid:
|
||||
Log.Debug($"Torrent {torrent.RdId} removing torrents from Real Debrid, no files");
|
||||
await _torrents.Delete(torrent.TorrentId, false, true, false);
|
||||
break;
|
||||
case TorrentFinishedAction.None:
|
||||
Log.Debug($"Torrent {torrent.RdId} not removing torrents or files");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ using System.Threading.Tasks;
|
|||
using MonoTorrent;
|
||||
using Newtonsoft.Json;
|
||||
using RDNET;
|
||||
using RdtClient.Data.Data;
|
||||
using RdtClient.Data.Enums;
|
||||
using RdtClient.Service.Models;
|
||||
using ITorrentData = RdtClient.Data.Data.ITorrentData;
|
||||
using Torrent = RdtClient.Data.Models.Data.Torrent;
|
||||
|
||||
namespace RdtClient.Service.Services
|
||||
|
|
@ -19,10 +19,24 @@ namespace RdtClient.Service.Services
|
|||
Task<IList<Torrent>> Get();
|
||||
Task<Torrent> GetByHash(String hash);
|
||||
Task UpdateCategory(String hash, String category);
|
||||
Task UploadMagnet(String magnetLink, String category, Boolean autoDelete);
|
||||
Task UploadFile(Byte[] bytes, String category, Boolean autoDelete);
|
||||
Task<List<String>> GetAvailableFiles(String hash);
|
||||
|
||||
Task UploadMagnet(String magnetLink,
|
||||
String category,
|
||||
TorrentDownloadAction downloadAction,
|
||||
TorrentFinishedAction finishedAction,
|
||||
Int32 downloadMinSize,
|
||||
String downloadManualFiles);
|
||||
|
||||
Task UploadFile(Byte[] bytes,
|
||||
String category,
|
||||
TorrentDownloadAction downloadAction,
|
||||
TorrentFinishedAction finishedAction,
|
||||
Int32 downloadMinSize,
|
||||
String downloadManualFiles);
|
||||
|
||||
Task<List<TorrentInstantAvailabilityFile>> GetAvailableFiles(String hash);
|
||||
Task SelectFiles(Guid torrentId, IList<String> fileIds);
|
||||
Task CheckForLinks(Guid torrentId);
|
||||
Task Delete(Guid torrentId, Boolean deleteData, Boolean deleteRdTorrent, Boolean deleteLocalFiles);
|
||||
Task<String> UnrestrictLink(Guid downloadId);
|
||||
Task Download(Guid downloadId);
|
||||
|
|
@ -30,6 +44,7 @@ namespace RdtClient.Service.Services
|
|||
void Reset();
|
||||
Task<Profile> GetProfile();
|
||||
Task UpdateComplete(Guid torrentId, DateTimeOffset datetime);
|
||||
Task UpdateFilesSelected(Guid torrentId, DateTimeOffset datetime);
|
||||
Task Update();
|
||||
Task Retry(Guid id, Int32 retry);
|
||||
}
|
||||
|
|
@ -39,7 +54,7 @@ namespace RdtClient.Service.Services
|
|||
private static RdNetClient _rdtNetClient;
|
||||
|
||||
private static readonly SemaphoreSlim SemaphoreSlim = new(1, 1);
|
||||
|
||||
|
||||
private readonly IDownloads _downloads;
|
||||
private readonly ISettings _settings;
|
||||
private readonly ITorrentData _torrentData;
|
||||
|
|
@ -51,28 +66,10 @@ namespace RdtClient.Service.Services
|
|||
_downloads = downloads;
|
||||
}
|
||||
|
||||
private RdNetClient GetRdNetClient()
|
||||
{
|
||||
if (_rdtNetClient == null)
|
||||
{
|
||||
var apiKey = _settings.GetString("RealDebridApiKey")
|
||||
.Result;
|
||||
|
||||
if (String.IsNullOrWhiteSpace(apiKey))
|
||||
{
|
||||
throw new Exception("RealDebrid API Key not set in the settings");
|
||||
}
|
||||
|
||||
_rdtNetClient = new RdNetClient("X245A4XAIBGVM", null, null, null, apiKey);
|
||||
}
|
||||
|
||||
return _rdtNetClient;
|
||||
}
|
||||
|
||||
public async Task<IList<Torrent>> Get()
|
||||
{
|
||||
var torrents = await _torrentData.Get();
|
||||
|
||||
|
||||
foreach (var torrent in torrents)
|
||||
{
|
||||
foreach (var download in torrent.Downloads)
|
||||
|
|
@ -83,7 +80,7 @@ namespace RdtClient.Service.Services
|
|||
download.BytesTotal = downloadClient.BytesTotal;
|
||||
download.BytesDone = downloadClient.BytesDone;
|
||||
}
|
||||
|
||||
|
||||
if (TorrentRunner.ActiveUnpackClients.TryGetValue(download.DownloadId, out var unpackClient))
|
||||
{
|
||||
download.BytesTotal = unpackClient.BytesTotal;
|
||||
|
|
@ -95,18 +92,6 @@ namespace RdtClient.Service.Services
|
|||
return torrents;
|
||||
}
|
||||
|
||||
public async Task<Torrent> GetById(Guid torrentId)
|
||||
{
|
||||
var torrent = await _torrentData.GetById(torrentId);
|
||||
|
||||
if (torrent != null)
|
||||
{
|
||||
await Update(torrent);
|
||||
}
|
||||
|
||||
return torrent;
|
||||
}
|
||||
|
||||
public async Task<Torrent> GetByHash(String hash)
|
||||
{
|
||||
var torrent = await _torrentData.GetByHash(hash);
|
||||
|
|
@ -131,7 +116,12 @@ namespace RdtClient.Service.Services
|
|||
await _torrentData.UpdateCategory(torrent.TorrentId, category);
|
||||
}
|
||||
|
||||
public async Task UploadMagnet(String magnetLink, String category, Boolean autoDelete)
|
||||
public async Task UploadMagnet(String magnetLink,
|
||||
String category,
|
||||
TorrentDownloadAction downloadAction,
|
||||
TorrentFinishedAction finishedAction,
|
||||
Int32 downloadMinSize,
|
||||
String downloadManualFiles)
|
||||
{
|
||||
MagnetLink magnet;
|
||||
|
||||
|
|
@ -146,10 +136,15 @@ namespace RdtClient.Service.Services
|
|||
|
||||
var rdTorrent = await GetRdNetClient().AddTorrentMagnetAsync(magnetLink);
|
||||
|
||||
await Add(rdTorrent.Id, magnet.InfoHash.ToHex(), category, autoDelete, magnetLink, false);
|
||||
await Add(rdTorrent.Id, magnet.InfoHash.ToHex(), category, downloadAction, finishedAction, downloadMinSize, downloadManualFiles, magnetLink, false);
|
||||
}
|
||||
|
||||
public async Task UploadFile(Byte[] bytes, String category, Boolean autoDelete)
|
||||
public async Task UploadFile(Byte[] bytes,
|
||||
String category,
|
||||
TorrentDownloadAction downloadAction,
|
||||
TorrentFinishedAction finishedAction,
|
||||
Int32 downloadMinSize,
|
||||
String downloadManualFiles)
|
||||
{
|
||||
MonoTorrent.Torrent torrent;
|
||||
|
||||
|
|
@ -165,44 +160,36 @@ namespace RdtClient.Service.Services
|
|||
}
|
||||
|
||||
var rdTorrent = await GetRdNetClient().AddTorrentFileAsync(bytes);
|
||||
|
||||
await Add(rdTorrent.Id, torrent.InfoHash.ToHex(), category, autoDelete, fileAsBase64, true);
|
||||
|
||||
await Add(rdTorrent.Id, torrent.InfoHash.ToHex(), category, downloadAction, finishedAction, downloadMinSize, downloadManualFiles, fileAsBase64, true);
|
||||
}
|
||||
|
||||
public async Task<List<String>> GetAvailableFiles(String hash)
|
||||
public async Task<List<TorrentInstantAvailabilityFile>> GetAvailableFiles(String hash)
|
||||
{
|
||||
var result = await GetRdNetClient().GetAvailableFiles(hash);
|
||||
|
||||
var files = result.SelectMany(m => m.Value).SelectMany(m => m.Value).SelectMany(m => m.Values);
|
||||
|
||||
var groups = files.GroupBy(m => m.Filename);
|
||||
var groups = files.GroupBy(m => $"{m.Filename}-{m.Filesize}");
|
||||
|
||||
return groups.Select(m => m.Key).ToList();
|
||||
return groups.Select(m => m.First()).ToList();
|
||||
}
|
||||
|
||||
public async Task SelectFiles(Guid torrentId, IList<String> fileIds)
|
||||
{
|
||||
var torrent = await GetById(torrentId);
|
||||
|
||||
RDNET.Torrent rdTorrent = null;
|
||||
await GetRdNetClient().SelectTorrentFilesAsync(torrent.RdId, fileIds.ToArray());
|
||||
}
|
||||
|
||||
for (var i = 0; i < 5; i++)
|
||||
{
|
||||
await GetRdNetClient().SelectTorrentFilesAsync(torrent.RdId, fileIds.ToArray());
|
||||
public async Task CheckForLinks(Guid torrentId)
|
||||
{
|
||||
var torrent = await GetById(torrentId);
|
||||
|
||||
await Task.Delay(5000);
|
||||
var rdTorrent = await GetRdNetClient().GetTorrentInfoAsync(torrent.RdId);
|
||||
|
||||
rdTorrent = await GetRdNetClient().GetTorrentInfoAsync(torrent.RdId);
|
||||
|
||||
if (fileIds.Count == rdTorrent.Links.Count)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
await Task.Delay(10000);
|
||||
}
|
||||
|
||||
if (rdTorrent == null)
|
||||
// Sometimes RD will give you 1 rar with all files, sometimes it will give you 1 link per file.
|
||||
if (torrent.Files.Count != rdTorrent.Links.Count && rdTorrent.Links.Count != 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -254,7 +241,7 @@ namespace RdtClient.Service.Services
|
|||
{
|
||||
var downloadPath = await DownloadPath(torrent);
|
||||
downloadPath = Path.Combine(downloadPath, torrent.RdName);
|
||||
|
||||
|
||||
if (Directory.Exists(downloadPath))
|
||||
{
|
||||
var retry = 0;
|
||||
|
|
@ -270,6 +257,7 @@ namespace RdtClient.Service.Services
|
|||
catch
|
||||
{
|
||||
retry++;
|
||||
|
||||
if (retry >= 3)
|
||||
{
|
||||
throw;
|
||||
|
|
@ -332,33 +320,6 @@ namespace RdtClient.Service.Services
|
|||
return profile;
|
||||
}
|
||||
|
||||
private async Task Add(String rdTorrentId, String infoHash, String category, Boolean autoDelete, String fileOrMagnetContents, Boolean isFile)
|
||||
{
|
||||
var w = await SemaphoreSlim.WaitAsync(60000);
|
||||
if (!w)
|
||||
{
|
||||
throw new Exception("Unable to add torrent, could not obtain lock");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var torrent = await _torrentData.GetByHash(infoHash);
|
||||
|
||||
if (torrent != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var newTorrent = await _torrentData.Add(rdTorrentId, infoHash, category, autoDelete, fileOrMagnetContents, isFile);
|
||||
|
||||
await Update(newTorrent);
|
||||
}
|
||||
finally
|
||||
{
|
||||
SemaphoreSlim.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Update()
|
||||
{
|
||||
var w = await SemaphoreSlim.WaitAsync(1);
|
||||
|
|
@ -382,7 +343,7 @@ namespace RdtClient.Service.Services
|
|||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
await Update(torrent);
|
||||
}
|
||||
|
||||
|
|
@ -420,11 +381,16 @@ namespace RdtClient.Service.Services
|
|||
{
|
||||
var bytes = Convert.FromBase64String(torrent.FileOrMagnet);
|
||||
|
||||
await UploadFile(bytes, torrent.Category, torrent.AutoDelete);
|
||||
await UploadFile(bytes, torrent.Category, torrent.DownloadAction, torrent.FinishedAction, torrent.DownloadMinSize, torrent.DownloadManualFiles);
|
||||
}
|
||||
else
|
||||
{
|
||||
await UploadMagnet(torrent.FileOrMagnet, torrent.Category, torrent.AutoDelete);
|
||||
await UploadMagnet(torrent.FileOrMagnet,
|
||||
torrent.Category,
|
||||
torrent.DownloadAction,
|
||||
torrent.FinishedAction,
|
||||
torrent.DownloadMinSize,
|
||||
torrent.DownloadManualFiles);
|
||||
}
|
||||
}
|
||||
else if (retry == 1)
|
||||
|
|
@ -445,10 +411,89 @@ namespace RdtClient.Service.Services
|
|||
await _torrentData.UpdateComplete(torrentId, datetime);
|
||||
}
|
||||
|
||||
public async Task UpdateFilesSelected(Guid torrentId, DateTimeOffset datetime)
|
||||
{
|
||||
await _torrentData.UpdateFilesSelected(torrentId, datetime);
|
||||
}
|
||||
|
||||
private RdNetClient GetRdNetClient()
|
||||
{
|
||||
if (_rdtNetClient == null)
|
||||
{
|
||||
var apiKey = _settings.GetString("RealDebridApiKey")
|
||||
.Result;
|
||||
|
||||
if (String.IsNullOrWhiteSpace(apiKey))
|
||||
{
|
||||
throw new Exception("RealDebrid API Key not set in the settings");
|
||||
}
|
||||
|
||||
_rdtNetClient = new RdNetClient("X245A4XAIBGVM", null, null, null, apiKey);
|
||||
}
|
||||
|
||||
return _rdtNetClient;
|
||||
}
|
||||
|
||||
public async Task<Torrent> GetById(Guid torrentId)
|
||||
{
|
||||
var torrent = await _torrentData.GetById(torrentId);
|
||||
|
||||
if (torrent != null)
|
||||
{
|
||||
await Update(torrent);
|
||||
}
|
||||
|
||||
return torrent;
|
||||
}
|
||||
|
||||
private async Task Add(String rdTorrentId,
|
||||
String infoHash,
|
||||
String category,
|
||||
TorrentDownloadAction downloadAction,
|
||||
TorrentFinishedAction finishedAction,
|
||||
Int32 downloadMinSize,
|
||||
String downloadManualFiles,
|
||||
String fileOrMagnetContents,
|
||||
Boolean isFile)
|
||||
{
|
||||
var w = await SemaphoreSlim.WaitAsync(60000);
|
||||
|
||||
if (!w)
|
||||
{
|
||||
throw new Exception("Unable to add torrent, could not obtain lock");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var torrent = await _torrentData.GetByHash(infoHash);
|
||||
|
||||
if (torrent != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var newTorrent = await _torrentData.Add(rdTorrentId,
|
||||
infoHash,
|
||||
category,
|
||||
downloadAction,
|
||||
finishedAction,
|
||||
downloadMinSize,
|
||||
downloadManualFiles,
|
||||
fileOrMagnetContents,
|
||||
isFile);
|
||||
|
||||
await Update(newTorrent);
|
||||
}
|
||||
finally
|
||||
{
|
||||
SemaphoreSlim.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<String> DownloadPath(Torrent torrent)
|
||||
{
|
||||
var settingDownloadPath = await _settings.GetString("DownloadPath");
|
||||
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(torrent.Category))
|
||||
{
|
||||
settingDownloadPath = Path.Combine(settingDownloadPath, torrent.Category);
|
||||
|
|
@ -459,10 +504,11 @@ namespace RdtClient.Service.Services
|
|||
|
||||
private async Task Update(Torrent torrent)
|
||||
{
|
||||
var originalTorrent = JsonConvert.SerializeObject(torrent, new JsonSerializerSettings
|
||||
{
|
||||
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
|
||||
});
|
||||
var originalTorrent = JsonConvert.SerializeObject(torrent,
|
||||
new JsonSerializerSettings
|
||||
{
|
||||
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
|
||||
});
|
||||
|
||||
var rdTorrent = await GetRdNetClient().GetTorrentInfoAsync(torrent.RdId);
|
||||
|
||||
|
|
@ -515,10 +561,11 @@ namespace RdtClient.Service.Services
|
|||
_ => RealDebridStatus.Error
|
||||
};
|
||||
|
||||
var newTorrent = JsonConvert.SerializeObject(torrent, new JsonSerializerSettings
|
||||
{
|
||||
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
|
||||
});
|
||||
var newTorrent = JsonConvert.SerializeObject(torrent,
|
||||
new JsonSerializerSettings
|
||||
{
|
||||
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
|
||||
});
|
||||
|
||||
if (originalTorrent != newTorrent)
|
||||
{
|
||||
|
|
@ -526,4 +573,4 @@ namespace RdtClient.Service.Services
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -248,13 +248,13 @@ namespace RdtClient.Web.Controllers
|
|||
{
|
||||
if (url.StartsWith("magnet"))
|
||||
{
|
||||
await _qBittorrent.TorrentsAddMagnet(url.Trim(), request.Category, false);
|
||||
await _qBittorrent.TorrentsAddMagnet(url.Trim(), request.Category);
|
||||
}
|
||||
else if (url.StartsWith("http"))
|
||||
{
|
||||
var httpClient = new HttpClient();
|
||||
var result = await httpClient.GetByteArrayAsync(url);
|
||||
await _qBittorrent.TorrentsAddFile(result, request.Category, false);
|
||||
await _qBittorrent.TorrentsAddFile(result, request.Category);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -279,7 +279,7 @@ namespace RdtClient.Web.Controllers
|
|||
await file.CopyToAsync(target);
|
||||
var fileBytes = target.ToArray();
|
||||
|
||||
await _qBittorrent.TorrentsAddFile(fileBytes, request.Category, false);
|
||||
await _qBittorrent.TorrentsAddFile(fileBytes, request.Category);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Authorization;
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using MonoTorrent;
|
||||
using RdtClient.Data.Enums;
|
||||
using RdtClient.Service.Helpers;
|
||||
using RdtClient.Service.Services;
|
||||
using Torrent = RdtClient.Data.Models.Data.Torrent;
|
||||
|
|
@ -17,8 +18,8 @@ namespace RdtClient.Web.Controllers
|
|||
[Route("Api/Torrents")]
|
||||
public class TorrentsController : Controller
|
||||
{
|
||||
private readonly ITorrents _torrents;
|
||||
private readonly ITorrentRunner _torrentRunner;
|
||||
private readonly ITorrents _torrents;
|
||||
|
||||
public TorrentsController(ITorrents torrents, ITorrentRunner torrentRunner)
|
||||
{
|
||||
|
|
@ -31,18 +32,18 @@ namespace RdtClient.Web.Controllers
|
|||
public async Task<ActionResult<IList<Torrent>>> Get()
|
||||
{
|
||||
var results = await _torrents.Get();
|
||||
|
||||
|
||||
// Prevent infinite recursion when serializing
|
||||
foreach (var file in results.SelectMany(torrent => torrent.Downloads))
|
||||
{
|
||||
file.Torrent = null;
|
||||
}
|
||||
|
||||
|
||||
return Ok(results);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for debugging only. Force a tick.
|
||||
/// Used for debugging only. Force a tick.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
|
|
@ -73,7 +74,7 @@ namespace RdtClient.Web.Controllers
|
|||
|
||||
var bytes = memoryStream.ToArray();
|
||||
|
||||
await _torrents.UploadFile(bytes, null, formData.AutoDelete);
|
||||
await _torrents.UploadFile(bytes, formData.Category, formData.DownloadAction, formData.FinishedAction, formData.DownloadMinSize, formData.DownloadManualFiles);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
|
@ -82,11 +83,16 @@ namespace RdtClient.Web.Controllers
|
|||
[Route("UploadMagnet")]
|
||||
public async Task<ActionResult> UploadMagnet([FromBody] TorrentControllerUploadMagnetRequest request)
|
||||
{
|
||||
await _torrents.UploadMagnet(request.MagnetLink, null, request.AutoDelete);
|
||||
await _torrents.UploadMagnet(request.MagnetLink,
|
||||
request.Category,
|
||||
request.DownloadAction,
|
||||
request.FinishedAction,
|
||||
request.DownloadMinSize,
|
||||
request.DownloadManualFiles);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
|
||||
[HttpPost]
|
||||
[Route("CheckFiles")]
|
||||
public async Task<ActionResult> CheckFiles([FromForm] IFormFile file)
|
||||
|
|
@ -130,7 +136,7 @@ namespace RdtClient.Web.Controllers
|
|||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
|
||||
[HttpPost]
|
||||
[Route("Retry/{id}")]
|
||||
public async Task<ActionResult> Retry(Guid id, [FromBody] TorrentControllerRetryRequest request)
|
||||
|
|
@ -143,13 +149,21 @@ namespace RdtClient.Web.Controllers
|
|||
|
||||
public class TorrentControllerUploadFileRequest
|
||||
{
|
||||
public Boolean AutoDelete { get; set; }
|
||||
public String Category { get; set; }
|
||||
public TorrentDownloadAction DownloadAction { get; set; }
|
||||
public TorrentFinishedAction FinishedAction { get; set; }
|
||||
public Int32 DownloadMinSize { get; set; }
|
||||
public String DownloadManualFiles { get; set; }
|
||||
}
|
||||
|
||||
public class TorrentControllerUploadMagnetRequest
|
||||
{
|
||||
public String MagnetLink { get; set; }
|
||||
public Boolean AutoDelete { get; set; }
|
||||
public String Category { get; set; }
|
||||
public TorrentDownloadAction DownloadAction { get; set; }
|
||||
public TorrentFinishedAction FinishedAction { get; set; }
|
||||
public Int32 DownloadMinSize { get; set; }
|
||||
public String DownloadManualFiles { get; set; }
|
||||
}
|
||||
|
||||
public class TorrentControllerDeleteRequest
|
||||
|
|
@ -168,4 +182,4 @@ namespace RdtClient.Web.Controllers
|
|||
{
|
||||
public String MagnetLink { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue