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"
|
"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": {
|
"@angular/cli": {
|
||||||
"version": "12.1.2",
|
"version": "12.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-12.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-12.1.2.tgz",
|
||||||
|
|
@ -469,6 +486,14 @@
|
||||||
"tslib": "^2.2.0"
|
"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": {
|
"@angular/forms": {
|
||||||
"version": "12.1.2",
|
"version": "12.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-12.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-12.1.2.tgz",
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,11 @@
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "~12.1.2",
|
"@angular/animations": "~12.1.2",
|
||||||
|
"@angular/cdk": "^12.1.2",
|
||||||
"@angular/common": "~12.1.2",
|
"@angular/common": "~12.1.2",
|
||||||
"@angular/compiler": "~12.1.2",
|
"@angular/compiler": "~12.1.2",
|
||||||
"@angular/core": "~12.1.2",
|
"@angular/core": "~12.1.2",
|
||||||
|
"@angular/flex-layout": "^12.0.0-beta.34",
|
||||||
"@angular/forms": "~12.1.2",
|
"@angular/forms": "~12.1.2",
|
||||||
"@angular/platform-browser": "~12.1.2",
|
"@angular/platform-browser": "~12.1.2",
|
||||||
"@angular/platform-browser-dynamic": "~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 { NgModule } from '@angular/core';
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { AddNewTorrentComponent } from './add-new-torrent/add-new-torrent.component';
|
||||||
import { AuthResolverService } from './auth-resolver.service';
|
import { AuthResolverService } from './auth-resolver.service';
|
||||||
import { LoginComponent } from './login/login.component';
|
import { LoginComponent } from './login/login.component';
|
||||||
import { MainLayoutComponent } from './main-layout/main-layout.component';
|
import { MainLayoutComponent } from './main-layout/main-layout.component';
|
||||||
|
import { SettingsComponent } from './settings/settings.component';
|
||||||
import { SetupComponent } from './setup/setup.component';
|
import { SetupComponent } from './setup/setup.component';
|
||||||
|
import { TorrentTableComponent } from './torrent-table/torrent-table.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
|
|
@ -20,6 +23,20 @@ const routes: Routes = [
|
||||||
resolve: {
|
resolve: {
|
||||||
isLoggedIn: AuthResolverService,
|
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 { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
|
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
import { curray } from 'curray';
|
import { curray } from 'curray';
|
||||||
import { FileSizePipe, NgxFilesizeModule } from 'ngx-filesize';
|
import { FileSizePipe, NgxFilesizeModule } from 'ngx-filesize';
|
||||||
|
import { AddNewTorrentComponent } from './add-new-torrent/add-new-torrent.component';
|
||||||
import { AppRoutingModule } from './app-routing.module';
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
import { AuthInterceptor } from './auth.interceptor';
|
import { AuthInterceptor } from './auth.interceptor';
|
||||||
import { DownloadStatusPipe } from './download-status.pipe';
|
import { DownloadStatusPipe } from './download-status.pipe';
|
||||||
import { LoginComponent } from './login/login.component';
|
import { LoginComponent } from './login/login.component';
|
||||||
import { MainLayoutComponent } from './main-layout/main-layout.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 { 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 { SetupComponent } from './setup/setup.component';
|
||||||
import { TorrentDownloadComponent } from './torrent-download/torrent-download.component';
|
import { TorrentDownloadComponent } from './torrent-download/torrent-download.component';
|
||||||
import { TorrentFileComponent } from './torrent-file/torrent-file.component';
|
import { TorrentFileComponent } from './torrent-file/torrent-file.component';
|
||||||
|
|
@ -38,7 +39,7 @@ curray();
|
||||||
SetupComponent,
|
SetupComponent,
|
||||||
TorrentDownloadComponent,
|
TorrentDownloadComponent,
|
||||||
],
|
],
|
||||||
imports: [BrowserModule, AppRoutingModule, FormsModule, HttpClientModule, NgxFilesizeModule],
|
imports: [BrowserModule, AppRoutingModule, FormsModule, HttpClientModule, NgxFilesizeModule, FlexLayoutModule],
|
||||||
providers: [FileSizePipe, { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }],
|
providers: [FileSizePipe, { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }],
|
||||||
bootstrap: [AppComponent],
|
bootstrap: [AppComponent],
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<app-navbar></app-navbar>
|
<app-navbar></app-navbar>
|
||||||
<section class="section main-list">
|
<section class="section main-list">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<app-torrent-table></app-torrent-table>
|
<router-outlet></router-outlet>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,11 @@ export class TorrentFile {
|
||||||
public download: Download;
|
public download: Download;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class TorrentFileAvailability {
|
||||||
|
public filename: string;
|
||||||
|
public filesize: number;
|
||||||
|
}
|
||||||
|
|
||||||
export enum RealDebridStatus {
|
export enum RealDebridStatus {
|
||||||
Processing = 0,
|
Processing = 0,
|
||||||
WaitingForFileSelection = 1,
|
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">
|
<nav class="navbar is-dark is-fixed-top" role="navigation" aria-label="main navigation">
|
||||||
<div class="navbar-brand">
|
<div class="navbar-brand">
|
||||||
<a class="navbar-item">
|
<a class="navbar-item" routerLink="/">
|
||||||
<img src="../../assets/logo.png" />
|
<img src="../../assets/logo.png" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|
@ -20,13 +20,19 @@
|
||||||
|
|
||||||
<div class="navbar-menu" [class.is-active]="showMobileMenu">
|
<div class="navbar-menu" [class.is-active]="showMobileMenu">
|
||||||
<div class="navbar-start">
|
<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">
|
<span class="icon">
|
||||||
<i class="fas fa-plus" aria-hidden="true"></i>
|
<i class="fas fa-plus" aria-hidden="true"></i>
|
||||||
</span>
|
</span>
|
||||||
<span>Add New Torrent</span>
|
<span>Add New Torrent</span>
|
||||||
</a>
|
</a>
|
||||||
<a class="navbar-item" (click)="this.showSettings = true">
|
<a class="navbar-item" routerLink="/settings">
|
||||||
<span class="icon">
|
<span class="icon">
|
||||||
<i class="fas fa-cog" aria-hidden="true"></i>
|
<i class="fas fa-cog" aria-hidden="true"></i>
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -55,6 +61,3 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</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 { 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 { Router } from '@angular/router';
|
||||||
|
import { AuthService } from '../auth.service';
|
||||||
|
import { Profile } from '../models/profile.model';
|
||||||
|
import { SettingsService } from '../settings.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-navbar',
|
selector: 'app-navbar',
|
||||||
|
|
@ -13,9 +12,6 @@ import { Router } from '@angular/router';
|
||||||
export class NavbarComponent implements OnInit {
|
export class NavbarComponent implements OnInit {
|
||||||
public showMobileMenu = false;
|
public showMobileMenu = false;
|
||||||
|
|
||||||
public showNewTorrent = false;
|
|
||||||
public showSettings = false;
|
|
||||||
|
|
||||||
public profile: Profile;
|
public profile: Profile;
|
||||||
|
|
||||||
constructor(private settingsService: SettingsService, private authService: AuthService, private router: Router) {}
|
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 { Setting } from 'src/app/models/setting.model';
|
||||||
import { SettingsService } from 'src/app/settings.service';
|
import { SettingsService } from 'src/app/settings.service';
|
||||||
|
|
||||||
|
|
@ -8,23 +8,8 @@ import { SettingsService } from 'src/app/settings.service';
|
||||||
styleUrls: ['./settings.component.scss'],
|
styleUrls: ['./settings.component.scss'],
|
||||||
})
|
})
|
||||||
export class SettingsComponent implements OnInit {
|
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 activeTab = 0;
|
||||||
|
|
||||||
public isActive = false;
|
|
||||||
|
|
||||||
public saving = false;
|
public saving = false;
|
||||||
public error: string;
|
public error: string;
|
||||||
|
|
||||||
|
|
@ -53,7 +38,9 @@ export class SettingsComponent implements OnInit {
|
||||||
|
|
||||||
constructor(private settingsService: SettingsService) {}
|
constructor(private settingsService: SettingsService) {}
|
||||||
|
|
||||||
ngOnInit(): void {}
|
ngOnInit(): void {
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
|
|
||||||
public reset(): void {
|
public reset(): void {
|
||||||
this.saving = false;
|
this.saving = false;
|
||||||
|
|
@ -142,10 +129,12 @@ export class SettingsComponent implements OnInit {
|
||||||
|
|
||||||
this.settingsService.update(settings).subscribe(
|
this.settingsService.update(settings).subscribe(
|
||||||
() => {
|
() => {
|
||||||
this.isActive = false;
|
setTimeout(() => {
|
||||||
this.openChange.emit(this.open);
|
this.saving = false;
|
||||||
|
}, 1000);
|
||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
|
this.saving = false;
|
||||||
this.error = err;
|
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 {
|
private getSetting(settings: Setting[], key: string): string {
|
||||||
const setting = settings.filter((m) => m.settingId === key);
|
const setting = settings.filter((m) => m.settingId === key);
|
||||||
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { HttpClient } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import * as signalR from '@microsoft/signalr';
|
import * as signalR from '@microsoft/signalr';
|
||||||
import { Observable, Subject } from 'rxjs';
|
import { Observable, Subject } from 'rxjs';
|
||||||
import { Torrent } from './models/torrent.model';
|
import { Torrent, TorrentFileAvailability } from './models/torrent.model';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
|
|
@ -31,30 +31,51 @@ export class TorrentService {
|
||||||
return this.http.get<Torrent[]>(`/Api/Torrents`);
|
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`, {
|
return this.http.post<void>(`/Api/Torrents/UploadMagnet`, {
|
||||||
magnetLink,
|
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();
|
const formData: FormData = new FormData();
|
||||||
formData.append('file', file);
|
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);
|
return this.http.post<void>(`/Api/Torrents/UploadFile`, formData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public checkFilesMagnet(magnetLink: string): Observable<string[]> {
|
public checkFilesMagnet(magnetLink: string): Observable<TorrentFileAvailability[]> {
|
||||||
return this.http.post<string[]>(`/Api/Torrents/CheckFilesMagnet`, {
|
return this.http.post<TorrentFileAvailability[]>(`/Api/Torrents/CheckFilesMagnet`, {
|
||||||
magnetLink,
|
magnetLink,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public checkFiles(file: File): Observable<string[]> {
|
public checkFiles(file: File): Observable<TorrentFileAvailability[]> {
|
||||||
const formData: FormData = new FormData();
|
const formData: FormData = new FormData();
|
||||||
formData.append('file', file);
|
formData.append('file', file);
|
||||||
return this.http.post<string[]>(`/Api/Torrents/CheckFiles`, formData);
|
return this.http.post<TorrentFileAvailability[]>(`/Api/Torrents/CheckFiles`, formData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public delete(
|
public delete(
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using RdtClient.Data.Enums;
|
||||||
using RdtClient.Data.Models.Data;
|
using RdtClient.Data.Models.Data;
|
||||||
|
|
||||||
namespace RdtClient.Data.Data
|
namespace RdtClient.Data.Data
|
||||||
|
|
@ -13,10 +14,21 @@ namespace RdtClient.Data.Data
|
||||||
Task<IList<Torrent>> Get();
|
Task<IList<Torrent>> Get();
|
||||||
Task<Torrent> GetById(Guid torrentId);
|
Task<Torrent> GetById(Guid torrentId);
|
||||||
Task<Torrent> GetByHash(String hash);
|
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 UpdateRdData(Torrent torrent);
|
||||||
Task UpdateCategory(Guid torrentId, String category);
|
Task UpdateCategory(Guid torrentId, String category);
|
||||||
Task UpdateComplete(Guid torrentId, DateTimeOffset? datetime);
|
Task UpdateComplete(Guid torrentId, DateTimeOffset? datetime);
|
||||||
|
Task UpdateFilesSelected(Guid torrentId, DateTimeOffset datetime);
|
||||||
Task Delete(Guid torrentId);
|
Task Delete(Guid torrentId);
|
||||||
Task VoidCache();
|
Task VoidCache();
|
||||||
}
|
}
|
||||||
|
|
@ -95,7 +107,15 @@ namespace RdtClient.Data.Data
|
||||||
return dbTorrent;
|
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
|
var torrent = new Torrent
|
||||||
{
|
{
|
||||||
|
|
@ -104,7 +124,10 @@ namespace RdtClient.Data.Data
|
||||||
RdId = realDebridId,
|
RdId = realDebridId,
|
||||||
Hash = hash.ToLower(),
|
Hash = hash.ToLower(),
|
||||||
Category = category,
|
Category = category,
|
||||||
AutoDelete = autoDelete,
|
DownloadAction = downloadAction,
|
||||||
|
FinishedAction = finishedAction,
|
||||||
|
DownloadMinSize = downloadMinSize,
|
||||||
|
DownloadManualFiles = downloadManualFiles,
|
||||||
FileOrMagnet = fileOrMagnetContents,
|
FileOrMagnet = fileOrMagnetContents,
|
||||||
IsFile = isFile
|
IsFile = isFile
|
||||||
};
|
};
|
||||||
|
|
@ -176,6 +199,17 @@ namespace RdtClient.Data.Data
|
||||||
await VoidCache();
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task Delete(Guid torrentId)
|
public async Task Delete(Guid torrentId)
|
||||||
{
|
{
|
||||||
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
|
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
|
||||||
|
|
|
||||||
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
|
#pragma warning disable 612, 618
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasAnnotation("ProductVersion", "5.0.3");
|
.HasAnnotation("ProductVersion", "5.0.8");
|
||||||
|
|
||||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||||
{
|
{
|
||||||
|
|
@ -285,18 +285,30 @@ namespace RdtClient.Data.Migrations
|
||||||
b.Property<DateTimeOffset>("Added")
|
b.Property<DateTimeOffset>("Added")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<bool>("AutoDelete")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<string>("Category")
|
b.Property<string>("Category")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("Completed")
|
b.Property<DateTimeOffset?>("Completed")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("DownloadAction")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("DownloadManualFiles")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("DownloadMinSize")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<string>("FileOrMagnet")
|
b.Property<string>("FileOrMagnet")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("FilesSelected")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("FinishedAction")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<string>("Hash")
|
b.Property<string>("Hash")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,17 +17,15 @@ namespace RdtClient.Data.Models.Data
|
||||||
|
|
||||||
public String Category { 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 DateTimeOffset Added { get; set; }
|
public DateTimeOffset Added { get; set; }
|
||||||
|
public DateTimeOffset? FilesSelected { get; set; }
|
||||||
public DateTimeOffset? Completed { 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 String FileOrMagnet { get; set; }
|
||||||
public Boolean IsFile { 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<IList<TorrentFileItem>> TorrentFileContents(String hash);
|
||||||
Task<TorrentProperties> TorrentProperties(String hash);
|
Task<TorrentProperties> TorrentProperties(String hash);
|
||||||
Task TorrentsDelete(String hash, Boolean deleteFiles);
|
Task TorrentsDelete(String hash, Boolean deleteFiles);
|
||||||
Task TorrentsAddMagnet(String magnetLink, String category, Boolean autoDelete);
|
Task TorrentsAddMagnet(String magnetLink, String category);
|
||||||
Task TorrentsAddFile(Byte[] fileBytes, String category, Boolean autoDelete);
|
Task TorrentsAddFile(Byte[] fileBytes, String category);
|
||||||
Task TorrentsSetCategory(String hash, String category);
|
Task TorrentsSetCategory(String hash, String category);
|
||||||
Task<IDictionary<String, TorrentCategory>> TorrentsCategories();
|
Task<IDictionary<String, TorrentCategory>> TorrentsCategories();
|
||||||
Task CategoryCreate(String category);
|
Task CategoryCreate(String category);
|
||||||
|
|
@ -432,14 +432,24 @@ namespace RdtClient.Service.Services
|
||||||
await _torrents.Delete(torrent.TorrentId, true, true, deleteFiles);
|
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)
|
public async Task TorrentsSetCategory(String hash, String category)
|
||||||
|
|
|
||||||
|
|
@ -81,17 +81,6 @@ namespace RdtClient.Service.Services
|
||||||
return;
|
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");
|
var settingDownloadLimit = settings.GetNumber("DownloadLimit");
|
||||||
if (settingDownloadLimit < 1)
|
if (settingDownloadLimit < 1)
|
||||||
{
|
{
|
||||||
|
|
@ -365,35 +354,52 @@ namespace RdtClient.Service.Services
|
||||||
continue;
|
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.
|
// RealDebrid is waiting for file selection, select which files to download.
|
||||||
if ((torrent.RdStatus == RealDebridStatus.WaitingForFileSelection || torrent.RdStatus == RealDebridStatus.Finished) &&
|
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");
|
Log.Debug($"Torrent {torrent.RdId} selecting files");
|
||||||
|
|
||||||
var files = torrent.Files;
|
var files = torrent.Files;
|
||||||
|
|
||||||
if (settingOnlyDownloadAvailableFiles)
|
if (torrent.DownloadAction == TorrentDownloadAction.DownloadAvailableFiles)
|
||||||
{
|
{
|
||||||
Log.Debug($"Torrent {torrent.RdId} determining which files are already available");
|
Log.Debug($"Torrent {torrent.RdId} determining which files are already available");
|
||||||
|
|
||||||
var availableFiles = await _torrents.GetAvailableFiles(torrent.Hash);
|
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.Filename))).ToList();
|
||||||
{
|
}
|
||||||
files = torrent.Files.Where(m => availableFiles.Any(f => m.Path.EndsWith(f))).ToList();
|
else if (torrent.DownloadAction == TorrentDownloadAction.DownloadAll)
|
||||||
|
{
|
||||||
Log.Debug($"Selecting {files.Count} available files for torrent {torrent.RdId}");
|
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($"Selecting {files.Count}/{torrent.Files.Count} files for torrent {torrent.RdId}");
|
||||||
{
|
|
||||||
Log.Debug($"Torrent {torrent.RdId} determining which files are over {settingMinFileSize} bytes");
|
|
||||||
|
|
||||||
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}");
|
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)}");
|
Log.Debug($"Selecting files for torrent {torrent.RdId}: {String.Join(", ", fileIds)}");
|
||||||
|
|
||||||
await _torrents.SelectFiles(torrent.TorrentId, fileIds);
|
await _torrents.SelectFiles(torrent.TorrentId, fileIds);
|
||||||
}
|
|
||||||
|
|
||||||
// If the torrent doesn't have any files at this point, don't process it further.
|
await _torrents.UpdateFilesSelected(torrent.TorrentId, DateTime.UtcNow);
|
||||||
if (torrent.Files.Count == 0)
|
|
||||||
{
|
|
||||||
Log.Debug($"No files found for torrent {torrent.RdId}!");
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RealDebrid finished downloading the torrent, process the file to host.
|
// 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);
|
await _torrents.UpdateComplete(torrent.TorrentId, DateTimeOffset.UtcNow);
|
||||||
|
|
||||||
// Remove the torrent from RealDebrid
|
switch (torrent.FinishedAction)
|
||||||
if (torrent.AutoDelete)
|
|
||||||
{
|
{
|
||||||
Log.Debug($"Torrent {torrent.RdId} removing");
|
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);
|
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 MonoTorrent;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using RDNET;
|
using RDNET;
|
||||||
|
using RdtClient.Data.Data;
|
||||||
using RdtClient.Data.Enums;
|
using RdtClient.Data.Enums;
|
||||||
using RdtClient.Service.Models;
|
using RdtClient.Service.Models;
|
||||||
using ITorrentData = RdtClient.Data.Data.ITorrentData;
|
|
||||||
using Torrent = RdtClient.Data.Models.Data.Torrent;
|
using Torrent = RdtClient.Data.Models.Data.Torrent;
|
||||||
|
|
||||||
namespace RdtClient.Service.Services
|
namespace RdtClient.Service.Services
|
||||||
|
|
@ -19,10 +19,24 @@ namespace RdtClient.Service.Services
|
||||||
Task<IList<Torrent>> Get();
|
Task<IList<Torrent>> Get();
|
||||||
Task<Torrent> GetByHash(String hash);
|
Task<Torrent> GetByHash(String hash);
|
||||||
Task UpdateCategory(String hash, String category);
|
Task UpdateCategory(String hash, String category);
|
||||||
Task UploadMagnet(String magnetLink, String category, Boolean autoDelete);
|
|
||||||
Task UploadFile(Byte[] bytes, String category, Boolean autoDelete);
|
Task UploadMagnet(String magnetLink,
|
||||||
Task<List<String>> GetAvailableFiles(String hash);
|
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 SelectFiles(Guid torrentId, IList<String> fileIds);
|
||||||
|
Task CheckForLinks(Guid torrentId);
|
||||||
Task Delete(Guid torrentId, Boolean deleteData, Boolean deleteRdTorrent, Boolean deleteLocalFiles);
|
Task Delete(Guid torrentId, Boolean deleteData, Boolean deleteRdTorrent, Boolean deleteLocalFiles);
|
||||||
Task<String> UnrestrictLink(Guid downloadId);
|
Task<String> UnrestrictLink(Guid downloadId);
|
||||||
Task Download(Guid downloadId);
|
Task Download(Guid downloadId);
|
||||||
|
|
@ -30,6 +44,7 @@ namespace RdtClient.Service.Services
|
||||||
void Reset();
|
void Reset();
|
||||||
Task<Profile> GetProfile();
|
Task<Profile> GetProfile();
|
||||||
Task UpdateComplete(Guid torrentId, DateTimeOffset datetime);
|
Task UpdateComplete(Guid torrentId, DateTimeOffset datetime);
|
||||||
|
Task UpdateFilesSelected(Guid torrentId, DateTimeOffset datetime);
|
||||||
Task Update();
|
Task Update();
|
||||||
Task Retry(Guid id, Int32 retry);
|
Task Retry(Guid id, Int32 retry);
|
||||||
}
|
}
|
||||||
|
|
@ -51,24 +66,6 @@ namespace RdtClient.Service.Services
|
||||||
_downloads = downloads;
|
_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()
|
public async Task<IList<Torrent>> Get()
|
||||||
{
|
{
|
||||||
var torrents = await _torrentData.Get();
|
var torrents = await _torrentData.Get();
|
||||||
|
|
@ -95,18 +92,6 @@ namespace RdtClient.Service.Services
|
||||||
return torrents;
|
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)
|
public async Task<Torrent> GetByHash(String hash)
|
||||||
{
|
{
|
||||||
var torrent = await _torrentData.GetByHash(hash);
|
var torrent = await _torrentData.GetByHash(hash);
|
||||||
|
|
@ -131,7 +116,12 @@ namespace RdtClient.Service.Services
|
||||||
await _torrentData.UpdateCategory(torrent.TorrentId, category);
|
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;
|
MagnetLink magnet;
|
||||||
|
|
||||||
|
|
@ -146,10 +136,15 @@ namespace RdtClient.Service.Services
|
||||||
|
|
||||||
var rdTorrent = await GetRdNetClient().AddTorrentMagnetAsync(magnetLink);
|
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;
|
MonoTorrent.Torrent torrent;
|
||||||
|
|
||||||
|
|
@ -166,43 +161,35 @@ namespace RdtClient.Service.Services
|
||||||
|
|
||||||
var rdTorrent = await GetRdNetClient().AddTorrentFileAsync(bytes);
|
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 result = await GetRdNetClient().GetAvailableFiles(hash);
|
||||||
|
|
||||||
var files = result.SelectMany(m => m.Value).SelectMany(m => m.Value).SelectMany(m => m.Values);
|
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)
|
public async Task SelectFiles(Guid torrentId, IList<String> fileIds)
|
||||||
{
|
{
|
||||||
var torrent = await GetById(torrentId);
|
var torrent = await GetById(torrentId);
|
||||||
|
|
||||||
RDNET.Torrent rdTorrent = null;
|
await GetRdNetClient().SelectTorrentFilesAsync(torrent.RdId, fileIds.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
for (var i = 0; i < 5; i++)
|
public async Task CheckForLinks(Guid torrentId)
|
||||||
{
|
{
|
||||||
await GetRdNetClient().SelectTorrentFilesAsync(torrent.RdId, fileIds.ToArray());
|
var torrent = await GetById(torrentId);
|
||||||
|
|
||||||
await Task.Delay(5000);
|
var rdTorrent = await GetRdNetClient().GetTorrentInfoAsync(torrent.RdId);
|
||||||
|
|
||||||
rdTorrent = await GetRdNetClient().GetTorrentInfoAsync(torrent.RdId);
|
// 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)
|
||||||
if (fileIds.Count == rdTorrent.Links.Count)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
await Task.Delay(10000);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rdTorrent == null)
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -270,6 +257,7 @@ namespace RdtClient.Service.Services
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
retry++;
|
retry++;
|
||||||
|
|
||||||
if (retry >= 3)
|
if (retry >= 3)
|
||||||
{
|
{
|
||||||
throw;
|
throw;
|
||||||
|
|
@ -332,33 +320,6 @@ namespace RdtClient.Service.Services
|
||||||
return profile;
|
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()
|
public async Task Update()
|
||||||
{
|
{
|
||||||
var w = await SemaphoreSlim.WaitAsync(1);
|
var w = await SemaphoreSlim.WaitAsync(1);
|
||||||
|
|
@ -420,11 +381,16 @@ namespace RdtClient.Service.Services
|
||||||
{
|
{
|
||||||
var bytes = Convert.FromBase64String(torrent.FileOrMagnet);
|
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
|
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)
|
else if (retry == 1)
|
||||||
|
|
@ -445,6 +411,85 @@ namespace RdtClient.Service.Services
|
||||||
await _torrentData.UpdateComplete(torrentId, datetime);
|
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)
|
private async Task<String> DownloadPath(Torrent torrent)
|
||||||
{
|
{
|
||||||
var settingDownloadPath = await _settings.GetString("DownloadPath");
|
var settingDownloadPath = await _settings.GetString("DownloadPath");
|
||||||
|
|
@ -459,10 +504,11 @@ namespace RdtClient.Service.Services
|
||||||
|
|
||||||
private async Task Update(Torrent torrent)
|
private async Task Update(Torrent torrent)
|
||||||
{
|
{
|
||||||
var originalTorrent = JsonConvert.SerializeObject(torrent, new JsonSerializerSettings
|
var originalTorrent = JsonConvert.SerializeObject(torrent,
|
||||||
{
|
new JsonSerializerSettings
|
||||||
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
|
{
|
||||||
});
|
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
|
||||||
|
});
|
||||||
|
|
||||||
var rdTorrent = await GetRdNetClient().GetTorrentInfoAsync(torrent.RdId);
|
var rdTorrent = await GetRdNetClient().GetTorrentInfoAsync(torrent.RdId);
|
||||||
|
|
||||||
|
|
@ -515,10 +561,11 @@ namespace RdtClient.Service.Services
|
||||||
_ => RealDebridStatus.Error
|
_ => RealDebridStatus.Error
|
||||||
};
|
};
|
||||||
|
|
||||||
var newTorrent = JsonConvert.SerializeObject(torrent, new JsonSerializerSettings
|
var newTorrent = JsonConvert.SerializeObject(torrent,
|
||||||
{
|
new JsonSerializerSettings
|
||||||
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
|
{
|
||||||
});
|
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
|
||||||
|
});
|
||||||
|
|
||||||
if (originalTorrent != newTorrent)
|
if (originalTorrent != newTorrent)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -248,13 +248,13 @@ namespace RdtClient.Web.Controllers
|
||||||
{
|
{
|
||||||
if (url.StartsWith("magnet"))
|
if (url.StartsWith("magnet"))
|
||||||
{
|
{
|
||||||
await _qBittorrent.TorrentsAddMagnet(url.Trim(), request.Category, false);
|
await _qBittorrent.TorrentsAddMagnet(url.Trim(), request.Category);
|
||||||
}
|
}
|
||||||
else if (url.StartsWith("http"))
|
else if (url.StartsWith("http"))
|
||||||
{
|
{
|
||||||
var httpClient = new HttpClient();
|
var httpClient = new HttpClient();
|
||||||
var result = await httpClient.GetByteArrayAsync(url);
|
var result = await httpClient.GetByteArrayAsync(url);
|
||||||
await _qBittorrent.TorrentsAddFile(result, request.Category, false);
|
await _qBittorrent.TorrentsAddFile(result, request.Category);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -279,7 +279,7 @@ namespace RdtClient.Web.Controllers
|
||||||
await file.CopyToAsync(target);
|
await file.CopyToAsync(target);
|
||||||
var fileBytes = target.ToArray();
|
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.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using MonoTorrent;
|
using MonoTorrent;
|
||||||
|
using RdtClient.Data.Enums;
|
||||||
using RdtClient.Service.Helpers;
|
using RdtClient.Service.Helpers;
|
||||||
using RdtClient.Service.Services;
|
using RdtClient.Service.Services;
|
||||||
using Torrent = RdtClient.Data.Models.Data.Torrent;
|
using Torrent = RdtClient.Data.Models.Data.Torrent;
|
||||||
|
|
@ -17,8 +18,8 @@ namespace RdtClient.Web.Controllers
|
||||||
[Route("Api/Torrents")]
|
[Route("Api/Torrents")]
|
||||||
public class TorrentsController : Controller
|
public class TorrentsController : Controller
|
||||||
{
|
{
|
||||||
private readonly ITorrents _torrents;
|
|
||||||
private readonly ITorrentRunner _torrentRunner;
|
private readonly ITorrentRunner _torrentRunner;
|
||||||
|
private readonly ITorrents _torrents;
|
||||||
|
|
||||||
public TorrentsController(ITorrents torrents, ITorrentRunner torrentRunner)
|
public TorrentsController(ITorrents torrents, ITorrentRunner torrentRunner)
|
||||||
{
|
{
|
||||||
|
|
@ -42,7 +43,7 @@ namespace RdtClient.Web.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used for debugging only. Force a tick.
|
/// Used for debugging only. Force a tick.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
|
|
@ -73,7 +74,7 @@ namespace RdtClient.Web.Controllers
|
||||||
|
|
||||||
var bytes = memoryStream.ToArray();
|
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();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
@ -82,7 +83,12 @@ namespace RdtClient.Web.Controllers
|
||||||
[Route("UploadMagnet")]
|
[Route("UploadMagnet")]
|
||||||
public async Task<ActionResult> UploadMagnet([FromBody] TorrentControllerUploadMagnetRequest request)
|
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();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
@ -143,13 +149,21 @@ namespace RdtClient.Web.Controllers
|
||||||
|
|
||||||
public class TorrentControllerUploadFileRequest
|
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 class TorrentControllerUploadMagnetRequest
|
||||||
{
|
{
|
||||||
public String MagnetLink { get; set; }
|
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
|
public class TorrentControllerDeleteRequest
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue