diff --git a/client/package-lock.json b/client/package-lock.json
index c2af9d2..1bc9c26 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -291,6 +291,23 @@
"tslib": "^2.2.0"
}
},
+ "@angular/cdk": {
+ "version": "12.1.2",
+ "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-12.1.2.tgz",
+ "integrity": "sha512-ALupZejZDsVYcbNZcEH1cV8SDgVBL40FAwDnlSZxCgd0HOBHH0ZqQV+8z0uCQeMatoNM+SwmJ8Y1JXYh9Bqfiw==",
+ "requires": {
+ "parse5": "^5.0.0",
+ "tslib": "^2.2.0"
+ },
+ "dependencies": {
+ "parse5": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz",
+ "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==",
+ "optional": true
+ }
+ }
+ },
"@angular/cli": {
"version": "12.1.2",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-12.1.2.tgz",
@@ -469,6 +486,14 @@
"tslib": "^2.2.0"
}
},
+ "@angular/flex-layout": {
+ "version": "12.0.0-beta.34",
+ "resolved": "https://registry.npmjs.org/@angular/flex-layout/-/flex-layout-12.0.0-beta.34.tgz",
+ "integrity": "sha512-nLwKovXpyG+/U3Lbmfwt+q4ARupxtbPmFTZD0Y8oItFAV6/Oh9l+QQsNQa2VhOHAOrVagyDwcEM+SePtB5EmrQ==",
+ "requires": {
+ "tslib": "^2.1.0"
+ }
+ },
"@angular/forms": {
"version": "12.1.2",
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-12.1.2.tgz",
diff --git a/client/package.json b/client/package.json
index 8aa2fa3..1afb77b 100644
--- a/client/package.json
+++ b/client/package.json
@@ -13,9 +13,11 @@
"private": true,
"dependencies": {
"@angular/animations": "~12.1.2",
+ "@angular/cdk": "^12.1.2",
"@angular/common": "~12.1.2",
"@angular/compiler": "~12.1.2",
"@angular/core": "~12.1.2",
+ "@angular/flex-layout": "^12.0.0-beta.34",
"@angular/forms": "~12.1.2",
"@angular/platform-browser": "~12.1.2",
"@angular/platform-browser-dynamic": "~12.1.2",
diff --git a/client/src/app/add-new-torrent/add-new-torrent.component.html b/client/src/app/add-new-torrent/add-new-torrent.component.html
new file mode 100644
index 0000000..bd5af13
--- /dev/null
+++ b/client/src/app/add-new-torrent/add-new-torrent.component.html
@@ -0,0 +1,123 @@
+
+
+
+
Torrent file
+
+
+
+
+
+
+
+ Pick a torrent file...
+
+
+ {{ fileName }}
+
+
+
+
+
+
+
+
+
+
+
+
+
Download action
+
+
+ Download all files above a certain size
+ Download all available files on Real Debrid above a certain size
+ Pick files I want to download
+
+
+
+
+
+
Minimum file size to download
+
+
This setting does not apply to manually selected files.
+
+
+
+
Finished action
+
+
+ Do nothing
+ Remove torrent from Real Debrid and Real Debrid Client
+ Remove torrent from Real Debrid
+
+
+
+
+
+
Category
+
+
+
+
The category becomes a sub-folder in your main download path.
+
+
+
+
+
Available files
+
+ These files are available for immediate download from Real Debrid.
+ It is possible that there are more files in the torrent, which are not shown here.
+
+
+
+
+
+
+
+
+
diff --git a/client/src/app/add-new-torrent/add-new-torrent.component.scss b/client/src/app/add-new-torrent/add-new-torrent.component.scss
new file mode 100644
index 0000000..64f7f7d
--- /dev/null
+++ b/client/src/app/add-new-torrent/add-new-torrent.component.scss
@@ -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;
+}
diff --git a/client/src/app/add-new-torrent/add-new-torrent.component.ts b/client/src/app/add-new-torrent/add-new-torrent.component.ts
new file mode 100644
index 0000000..2a384fe
--- /dev/null
+++ b/client/src/app/add-new-torrent/add-new-torrent.component.ts
@@ -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;
+ }
+ }
+}
diff --git a/client/src/app/app-routing.module.ts b/client/src/app/app-routing.module.ts
index f16c983..9047896 100644
--- a/client/src/app/app-routing.module.ts
+++ b/client/src/app/app-routing.module.ts
@@ -1,9 +1,12 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
+import { AddNewTorrentComponent } from './add-new-torrent/add-new-torrent.component';
import { AuthResolverService } from './auth-resolver.service';
import { LoginComponent } from './login/login.component';
import { MainLayoutComponent } from './main-layout/main-layout.component';
+import { SettingsComponent } from './settings/settings.component';
import { SetupComponent } from './setup/setup.component';
+import { TorrentTableComponent } from './torrent-table/torrent-table.component';
const routes: Routes = [
{
@@ -20,6 +23,20 @@ const routes: Routes = [
resolve: {
isLoggedIn: AuthResolverService,
},
+ children: [
+ {
+ path: '',
+ component: TorrentTableComponent,
+ },
+ {
+ path: 'add',
+ component: AddNewTorrentComponent,
+ },
+ {
+ path: 'settings',
+ component: SettingsComponent,
+ },
+ ],
},
];
diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts
index 036ff92..8b35ce8 100644
--- a/client/src/app/app.module.ts
+++ b/client/src/app/app.module.ts
@@ -1,18 +1,19 @@
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { NgModule } from '@angular/core';
+import { FlexLayoutModule } from '@angular/flex-layout';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { curray } from 'curray';
import { FileSizePipe, NgxFilesizeModule } from 'ngx-filesize';
+import { AddNewTorrentComponent } from './add-new-torrent/add-new-torrent.component';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { AuthInterceptor } from './auth.interceptor';
import { DownloadStatusPipe } from './download-status.pipe';
import { LoginComponent } from './login/login.component';
import { MainLayoutComponent } from './main-layout/main-layout.component';
-import { AddNewTorrentComponent } from './navbar/add-new-torrent/add-new-torrent.component';
import { NavbarComponent } from './navbar/navbar.component';
-import { SettingsComponent } from './navbar/settings/settings.component';
+import { SettingsComponent } from './settings/settings.component';
import { SetupComponent } from './setup/setup.component';
import { TorrentDownloadComponent } from './torrent-download/torrent-download.component';
import { TorrentFileComponent } from './torrent-file/torrent-file.component';
@@ -38,7 +39,7 @@ curray();
SetupComponent,
TorrentDownloadComponent,
],
- imports: [BrowserModule, AppRoutingModule, FormsModule, HttpClientModule, NgxFilesizeModule],
+ imports: [BrowserModule, AppRoutingModule, FormsModule, HttpClientModule, NgxFilesizeModule, FlexLayoutModule],
providers: [FileSizePipe, { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }],
bootstrap: [AppComponent],
})
diff --git a/client/src/app/main-layout/main-layout.component.html b/client/src/app/main-layout/main-layout.component.html
index 1ad0a23..6df30e1 100644
--- a/client/src/app/main-layout/main-layout.component.html
+++ b/client/src/app/main-layout/main-layout.component.html
@@ -1,6 +1,6 @@
diff --git a/client/src/app/models/torrent.model.ts b/client/src/app/models/torrent.model.ts
index 68d8c2a..1a16267 100644
--- a/client/src/app/models/torrent.model.ts
+++ b/client/src/app/models/torrent.model.ts
@@ -35,6 +35,11 @@ export class TorrentFile {
public download: Download;
}
+export class TorrentFileAvailability {
+ public filename: string;
+ public filesize: number;
+}
+
export enum RealDebridStatus {
Processing = 0,
WaitingForFileSelection = 1,
diff --git a/client/src/app/navbar/add-new-torrent/add-new-torrent.component.html b/client/src/app/navbar/add-new-torrent/add-new-torrent.component.html
deleted file mode 100644
index 04832a0..0000000
--- a/client/src/app/navbar/add-new-torrent/add-new-torrent.component.html
+++ /dev/null
@@ -1,77 +0,0 @@
-
-
-
-
-
-
-
-
-
Torrent file
-
-
-
-
-
-
-
- Pick a torrent file...
-
-
- {{ fileName }}
-
-
-
-
-
-
-
-
- Remove torrent when finished downloading on host
-
-
- Cannot add torrent: {{ error | json }}
-
-
-
-
-
-
diff --git a/client/src/app/navbar/add-new-torrent/add-new-torrent.component.scss b/client/src/app/navbar/add-new-torrent/add-new-torrent.component.scss
deleted file mode 100644
index 4b5fc83..0000000
--- a/client/src/app/navbar/add-new-torrent/add-new-torrent.component.scss
+++ /dev/null
@@ -1,8 +0,0 @@
-.file-name {
- max-width: 24em;
- width: 24em;
-}
-
-.file-modal {
- width: calc(75% - 40px);
-}
diff --git a/client/src/app/navbar/add-new-torrent/add-new-torrent.component.ts b/client/src/app/navbar/add-new-torrent/add-new-torrent.component.ts
deleted file mode 100644
index 8d1ce08..0000000
--- a/client/src/app/navbar/add-new-torrent/add-new-torrent.component.ts
+++ /dev/null
@@ -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();
-
- 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);
- }
-}
diff --git a/client/src/app/navbar/navbar.component.html b/client/src/app/navbar/navbar.component.html
index 5cded01..50f9f04 100644
--- a/client/src/app/navbar/navbar.component.html
+++ b/client/src/app/navbar/navbar.component.html
@@ -1,6 +1,6 @@
-
+
@@ -20,13 +20,19 @@
-
-
-
diff --git a/client/src/app/navbar/navbar.component.ts b/client/src/app/navbar/navbar.component.ts
index e6bfedc..0aa7162 100644
--- a/client/src/app/navbar/navbar.component.ts
+++ b/client/src/app/navbar/navbar.component.ts
@@ -1,9 +1,8 @@
import { Component, OnInit } from '@angular/core';
-import { TorrentService } from '../torrent.service';
-import { SettingsService } from '../settings.service';
-import { Profile } from '../models/profile.model';
-import { AuthService } from '../auth.service';
import { Router } from '@angular/router';
+import { AuthService } from '../auth.service';
+import { Profile } from '../models/profile.model';
+import { SettingsService } from '../settings.service';
@Component({
selector: 'app-navbar',
@@ -13,9 +12,6 @@ import { Router } from '@angular/router';
export class NavbarComponent implements OnInit {
public showMobileMenu = false;
- public showNewTorrent = false;
- public showSettings = false;
-
public profile: Profile;
constructor(private settingsService: SettingsService, private authService: AuthService, private router: Router) {}
diff --git a/client/src/app/navbar/settings/settings.component.html b/client/src/app/navbar/settings/settings.component.html
deleted file mode 100644
index 5435135..0000000
--- a/client/src/app/navbar/settings/settings.component.html
+++ /dev/null
@@ -1,265 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
Log level
-
-
- Verbose
- Debug
- Information
- Warning
- Error
- Fatal
-
-
-
Recommended level is Warning, set to Debug to get the most info.
-
-
-
Download path
-
-
-
-
Path in the docker container to download files to (i.e. /data/downloads).
-
-
-
Mapped path
-
-
-
-
- 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.
-
-
-
-
-
Maximum unpack processes
-
-
-
-
Maximum amount of downloads that get unpacked on your host at the same time.
-
-
-
-
-
-
Download client
-
-
- Simple Downloader
- Multi Part Downloader
-
-
-
- Select which download client to use, see the
- README for the various options.
-
-
-
-
-
Temp Download path
-
-
-
-
- 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.
-
-
-
-
-
Maximum parallel downloads
-
-
-
-
Maximum amount of torrents that get downloaded to your host at the same time.
-
-
-
-
Parallel connections per download
-
-
-
-
- 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.
-
-
-
-
Download speed (in MB/s)
-
-
-
-
Maximum download speed in Megabytes per second. When set to 0 unlimited speed is used.
-
-
-
Proxy Server
-
-
-
-
Address of a proxy server.
-
-
-
-
-
-
- The following settings only apply when a torrent gets added through the qbittorrent API, usually Radarr or
- Sonarr.
-
-
Minimum file size to download
-
-
-
- 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.
-
-
-
-
-
-
-
-
- Only download available files on RealDebrid
-
-
- 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.
-
-
-
-
-
-
-
-
Test download path permissions
-
-
- Test permissions
-
-
- Could not test your download path
- {{ testPathError }}
-
-
-
Your download path looks good!
-
-
This will check if the download folder has write permissions.
-
-
-
-
Test RealDebrid download speed
-
-
- Test download speed
-
-
- Could not test your download speed
- {{ testDownloadSpeedError }}
-
-
-
- Download speed {{ testDownloadSpeedSuccess | filesize }}/s
-
-
-
- This will attempt to download a 10GB file from Real Debrid. When 50MB has been downloaded the test will
- stop.
-
-
-
-
-
Test download folder write speed
-
-
- Test write speed
-
-
- Could not test your download speed
- {{ testWriteSpeedError }}
-
-
-
- Write speed {{ testWriteSpeedSuccess | filesize }}/s
-
-
-
- This will write a small file to your download folder to see how fast it can write to it.
-
-
-
-
- 0">Error saving settings: {{ error }}
-
-
-
-
diff --git a/client/src/app/settings/settings.component.html b/client/src/app/settings/settings.component.html
new file mode 100644
index 0000000..c2cfce4
--- /dev/null
+++ b/client/src/app/settings/settings.component.html
@@ -0,0 +1,254 @@
+
+
+
+
+
+
+
Log level
+
+
+ Verbose
+ Debug
+ Information
+ Warning
+ Error
+ Fatal
+
+
+
Recommended level is Warning, set to Debug to get the most info.
+
+
+
Download path
+
+
+
+
Path in the docker container to download files to (i.e. /data/downloads).
+
+
+
Mapped path
+
+
+
+
+ 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.
+
+
+
+
+
Maximum unpack processes
+
+
+
+
Maximum amount of downloads that get unpacked on your host at the same time.
+
+
+
+
+
+
Download client
+
+
+ Simple Downloader
+ Multi Part Downloader
+
+
+
+ Select which download client to use, see the
+ README for the various options.
+
+
+
+
+
Temp Download path
+
+
+
+
+ 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.
+
+
+
+
+
Maximum parallel downloads
+
+
+
+
Maximum amount of torrents that get downloaded to your host at the same time.
+
+
+
+
Parallel connections per download
+
+
+
+
+ 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.
+
+
+
+
Download speed (in MB/s)
+
+
+
+
Maximum download speed in Megabytes per second. When set to 0 unlimited speed is used.
+
+
+
Proxy Server
+
+
+
+
Address of a proxy server.
+
+
+
+
+
+ The following settings only apply when a torrent gets added through the qbittorrent API, usually Radarr or Sonarr.
+
+
+
Minimum file size to download
+
+
+
+ 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.
+
+
+
+
+
+
+
+
+ Only download available files on RealDebrid
+
+
+ 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.
+
+
+
+
+
+
+
+
Test download path permissions
+
+
+ Test permissions
+
+
+ Could not test your download path
+ {{ testPathError }}
+
+
+
Your download path looks good!
+
+
This will check if the download folder has write permissions.
+
+
+
+
Test RealDebrid download speed
+
+
+ Test download speed
+
+
+ Could not test your download speed
+ {{ testDownloadSpeedError }}
+
+
+
+ Download speed {{ testDownloadSpeedSuccess | filesize }}/s
+
+
+
+ This will attempt to download a 10GB file from Real Debrid. When 50MB has been downloaded the test will stop.
+
+
+
+
+
Test download folder write speed
+
+
+ Test write speed
+
+
+ Could not test your download speed
+ {{ testWriteSpeedError }}
+
+
+
+ Write speed {{ testWriteSpeedSuccess | filesize }}/s
+
+
+
This will write a small file to your download folder to see how fast it can write to it.
+
+
+
+
+
+
0">Error saving settings: {{ error }}
+
+
+
+
+
+
+ Save Settings
+
+
+
diff --git a/client/src/app/navbar/settings/settings.component.scss b/client/src/app/settings/settings.component.scss
similarity index 100%
rename from client/src/app/navbar/settings/settings.component.scss
rename to client/src/app/settings/settings.component.scss
diff --git a/client/src/app/navbar/settings/settings.component.ts b/client/src/app/settings/settings.component.ts
similarity index 91%
rename from client/src/app/navbar/settings/settings.component.ts
rename to client/src/app/settings/settings.component.ts
index 985a773..dcf0c73 100644
--- a/client/src/app/navbar/settings/settings.component.ts
+++ b/client/src/app/settings/settings.component.ts
@@ -1,4 +1,4 @@
-import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
+import { Component, OnInit } from '@angular/core';
import { Setting } from 'src/app/models/setting.model';
import { SettingsService } from 'src/app/settings.service';
@@ -8,23 +8,8 @@ import { SettingsService } from 'src/app/settings.service';
styleUrls: ['./settings.component.scss'],
})
export class SettingsComponent implements OnInit {
- @Input()
- public get open(): boolean {
- return this.isActive;
- }
-
- public set open(val: boolean) {
- this.reset();
- this.isActive = val;
- }
-
- @Output()
- public openChange = new EventEmitter
();
-
public activeTab = 0;
- public isActive = false;
-
public saving = false;
public error: string;
@@ -53,7 +38,9 @@ export class SettingsComponent implements OnInit {
constructor(private settingsService: SettingsService) {}
- ngOnInit(): void {}
+ ngOnInit(): void {
+ this.reset();
+ }
public reset(): void {
this.saving = false;
@@ -142,10 +129,12 @@ export class SettingsComponent implements OnInit {
this.settingsService.update(settings).subscribe(
() => {
- this.isActive = false;
- this.openChange.emit(this.open);
+ setTimeout(() => {
+ this.saving = false;
+ }, 1000);
},
(err) => {
+ this.saving = false;
this.error = err;
}
);
@@ -201,11 +190,6 @@ export class SettingsComponent implements OnInit {
);
}
- public cancel(): void {
- this.isActive = false;
- this.openChange.emit(this.open);
- }
-
private getSetting(settings: Setting[], key: string): string {
const setting = settings.filter((m) => m.settingId === key);
diff --git a/client/src/app/torrent.service.ts b/client/src/app/torrent.service.ts
index 0360f8d..18edb14 100644
--- a/client/src/app/torrent.service.ts
+++ b/client/src/app/torrent.service.ts
@@ -2,7 +2,7 @@ import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import * as signalR from '@microsoft/signalr';
import { Observable, Subject } from 'rxjs';
-import { Torrent } from './models/torrent.model';
+import { Torrent, TorrentFileAvailability } from './models/torrent.model';
@Injectable({
providedIn: 'root',
@@ -31,30 +31,51 @@ export class TorrentService {
return this.http.get(`/Api/Torrents`);
}
- public uploadMagnet(magnetLink: string, autoDelete: boolean): Observable {
+ public uploadMagnet(
+ magnetLink: string,
+ category: string,
+ downloadAction: number,
+ finishedAction: number,
+ downloadMinSize: number,
+ downloadManualFiles: string
+ ): Observable {
return this.http.post(`/Api/Torrents/UploadMagnet`, {
magnetLink,
- autoDelete,
+ category,
+ downloadAction,
+ finishedAction,
+ downloadMinSize,
+ downloadManualFiles,
});
}
- public uploadFile(file: File, autoDelete: boolean): Observable {
+ public uploadFile(
+ file: File,
+ category: string,
+ downloadAction: number,
+ finishedAction: number,
+ downloadMinSize: number,
+ downloadManualFiles: string
+ ): Observable {
const formData: FormData = new FormData();
formData.append('file', file);
- formData.append('formData', JSON.stringify({ autoDelete }));
+ formData.append(
+ 'formData',
+ JSON.stringify({ category, downloadAction, finishedAction, downloadMinSize, downloadManualFiles })
+ );
return this.http.post(`/Api/Torrents/UploadFile`, formData);
}
- public checkFilesMagnet(magnetLink: string): Observable {
- return this.http.post(`/Api/Torrents/CheckFilesMagnet`, {
+ public checkFilesMagnet(magnetLink: string): Observable {
+ return this.http.post(`/Api/Torrents/CheckFilesMagnet`, {
magnetLink,
});
}
- public checkFiles(file: File): Observable {
+ public checkFiles(file: File): Observable {
const formData: FormData = new FormData();
formData.append('file', file);
- return this.http.post(`/Api/Torrents/CheckFiles`, formData);
+ return this.http.post(`/Api/Torrents/CheckFiles`, formData);
}
public delete(
diff --git a/server/RdtClient.Data/Data/TorrentData.cs b/server/RdtClient.Data/Data/TorrentData.cs
index 0bd73d6..8d6103c 100644
--- a/server/RdtClient.Data/Data/TorrentData.cs
+++ b/server/RdtClient.Data/Data/TorrentData.cs
@@ -4,6 +4,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
+using RdtClient.Data.Enums;
using RdtClient.Data.Models.Data;
namespace RdtClient.Data.Data
@@ -13,10 +14,21 @@ namespace RdtClient.Data.Data
Task> Get();
Task GetById(Guid torrentId);
Task GetByHash(String hash);
- Task Add(String realDebridId, String hash, String category, Boolean autoDelete, String fileOrMagnetContents, Boolean isFile);
+
+ Task Add(String realDebridId,
+ String hash,
+ String category,
+ TorrentDownloadAction downloadAction,
+ TorrentFinishedAction finishedAction,
+ Int32 downloadMinSize,
+ String downloadManualFiles,
+ String fileOrMagnetContents,
+ Boolean isFile);
+
Task UpdateRdData(Torrent torrent);
Task UpdateCategory(Guid torrentId, String category);
Task UpdateComplete(Guid torrentId, DateTimeOffset? datetime);
+ Task UpdateFilesSelected(Guid torrentId, DateTimeOffset datetime);
Task Delete(Guid torrentId);
Task VoidCache();
}
@@ -95,7 +107,15 @@ namespace RdtClient.Data.Data
return dbTorrent;
}
- public async Task Add(String realDebridId, String hash, String category, Boolean autoDelete, String fileOrMagnetContents, Boolean isFile)
+ public async Task Add(String realDebridId,
+ String hash,
+ String category,
+ TorrentDownloadAction downloadAction,
+ TorrentFinishedAction finishedAction,
+ Int32 downloadMinSize,
+ String downloadManualFiles,
+ String fileOrMagnetContents,
+ Boolean isFile)
{
var torrent = new Torrent
{
@@ -104,7 +124,10 @@ namespace RdtClient.Data.Data
RdId = realDebridId,
Hash = hash.ToLower(),
Category = category,
- AutoDelete = autoDelete,
+ DownloadAction = downloadAction,
+ FinishedAction = finishedAction,
+ DownloadMinSize = downloadMinSize,
+ DownloadManualFiles = downloadManualFiles,
FileOrMagnet = fileOrMagnetContents,
IsFile = isFile
};
@@ -148,7 +171,7 @@ namespace RdtClient.Data.Data
await VoidCache();
}
-
+
public async Task UpdateCategory(Guid torrentId, String category)
{
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
@@ -161,7 +184,7 @@ namespace RdtClient.Data.Data
dbTorrent.Category = category;
await _dataContext.SaveChangesAsync();
-
+
await VoidCache();
}
@@ -172,7 +195,18 @@ namespace RdtClient.Data.Data
dbTorrent.Completed = datetime;
await _dataContext.SaveChangesAsync();
-
+
+ await VoidCache();
+ }
+
+ public async Task UpdateFilesSelected(Guid torrentId, DateTimeOffset datetime)
+ {
+ var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
+
+ dbTorrent.FilesSelected = datetime;
+
+ await _dataContext.SaveChangesAsync();
+
await VoidCache();
}
@@ -188,7 +222,7 @@ namespace RdtClient.Data.Data
_dataContext.Torrents.Remove(dbTorrent);
await _dataContext.SaveChangesAsync();
-
+
await VoidCache();
}
@@ -206,4 +240,4 @@ namespace RdtClient.Data.Data
}
}
}
-}
\ No newline at end of file
+}
diff --git a/server/RdtClient.Data/Enums/TorrentDownloadAction.cs b/server/RdtClient.Data/Enums/TorrentDownloadAction.cs
new file mode 100644
index 0000000..7210567
--- /dev/null
+++ b/server/RdtClient.Data/Enums/TorrentDownloadAction.cs
@@ -0,0 +1,9 @@
+namespace RdtClient.Data.Enums
+{
+ public enum TorrentDownloadAction
+ {
+ DownloadAll = 0,
+ DownloadAvailableFiles = 1,
+ DownloadManual = 2
+ }
+}
diff --git a/server/RdtClient.Data/Enums/TorrentFinishedAction.cs b/server/RdtClient.Data/Enums/TorrentFinishedAction.cs
new file mode 100644
index 0000000..2643419
--- /dev/null
+++ b/server/RdtClient.Data/Enums/TorrentFinishedAction.cs
@@ -0,0 +1,9 @@
+namespace RdtClient.Data.Enums
+{
+ public enum TorrentFinishedAction
+ {
+ None = 0,
+ RemoveAllTorrents = 1,
+ RemoveRealDebrid = 2
+ }
+}
diff --git a/server/RdtClient.Data/Migrations/20210718222602_Torrents_Add_Actions.Designer.cs b/server/RdtClient.Data/Migrations/20210718222602_Torrents_Add_Actions.Designer.cs
new file mode 100644
index 0000000..c2c9a5d
--- /dev/null
+++ b/server/RdtClient.Data/Migrations/20210718222602_Torrents_Add_Actions.Designer.cs
@@ -0,0 +1,430 @@
+//
+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("Id")
+ .HasColumnType("TEXT");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedName")
+ .IsUnique()
+ .HasDatabaseName("RoleNameIndex");
+
+ b.ToTable("AspNetRoles");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ClaimType")
+ .HasColumnType("TEXT");
+
+ b.Property("ClaimValue")
+ .HasColumnType("TEXT");
+
+ b.Property("RoleId")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetRoleClaims");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT");
+
+ b.Property("AccessFailedCount")
+ .HasColumnType("INTEGER");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("TEXT");
+
+ b.Property("Email")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("EmailConfirmed")
+ .HasColumnType("INTEGER");
+
+ b.Property("LockoutEnabled")
+ .HasColumnType("INTEGER");
+
+ b.Property("LockoutEnd")
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedEmail")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedUserName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("PasswordHash")
+ .HasColumnType("TEXT");
+
+ b.Property("PhoneNumber")
+ .HasColumnType("TEXT");
+
+ b.Property("PhoneNumberConfirmed")
+ .HasColumnType("INTEGER");
+
+ b.Property("SecurityStamp")
+ .HasColumnType("TEXT");
+
+ b.Property("TwoFactorEnabled")
+ .HasColumnType("INTEGER");
+
+ b.Property("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", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ClaimType")
+ .HasColumnType("TEXT");
+
+ b.Property("ClaimValue")
+ .HasColumnType("TEXT");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserClaims");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.Property("LoginProvider")
+ .HasColumnType("TEXT");
+
+ b.Property("ProviderKey")
+ .HasColumnType("TEXT");
+
+ b.Property("ProviderDisplayName")
+ .HasColumnType("TEXT");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("LoginProvider", "ProviderKey");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserLogins");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("TEXT");
+
+ b.Property("RoleId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("UserId", "RoleId");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetUserRoles");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("TEXT");
+
+ b.Property("LoginProvider")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.Property("Value")
+ .HasColumnType("TEXT");
+
+ b.HasKey("UserId", "LoginProvider", "Name");
+
+ b.ToTable("AspNetUserTokens");
+ });
+
+ modelBuilder.Entity("RdtClient.Data.Models.Data.Download", b =>
+ {
+ b.Property("DownloadId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Added")
+ .HasColumnType("TEXT");
+
+ b.Property("Completed")
+ .HasColumnType("TEXT");
+
+ b.Property("DownloadFinished")
+ .HasColumnType("TEXT");
+
+ b.Property("DownloadQueued")
+ .HasColumnType("TEXT");
+
+ b.Property("DownloadStarted")
+ .HasColumnType("TEXT");
+
+ b.Property("Error")
+ .HasColumnType("TEXT");
+
+ b.Property("Link")
+ .HasColumnType("TEXT");
+
+ b.Property("Path")
+ .HasColumnType("TEXT");
+
+ b.Property("RetryCount")
+ .HasColumnType("INTEGER");
+
+ b.Property("TorrentId")
+ .HasColumnType("TEXT");
+
+ b.Property("UnpackingFinished")
+ .HasColumnType("TEXT");
+
+ b.Property("UnpackingQueued")
+ .HasColumnType("TEXT");
+
+ b.Property("UnpackingStarted")
+ .HasColumnType("TEXT");
+
+ b.HasKey("DownloadId");
+
+ b.HasIndex("TorrentId");
+
+ b.ToTable("Downloads");
+ });
+
+ modelBuilder.Entity("RdtClient.Data.Models.Data.Setting", b =>
+ {
+ b.Property("SettingId")
+ .HasColumnType("TEXT");
+
+ b.Property("Type")
+ .HasColumnType("TEXT");
+
+ b.Property("Value")
+ .HasColumnType("TEXT");
+
+ b.HasKey("SettingId");
+
+ b.ToTable("Settings");
+ });
+
+ modelBuilder.Entity("RdtClient.Data.Models.Data.Torrent", b =>
+ {
+ b.Property("TorrentId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Added")
+ .HasColumnType("TEXT");
+
+ b.Property("Category")
+ .HasColumnType("TEXT");
+
+ b.Property("Completed")
+ .HasColumnType("TEXT");
+
+ b.Property("DownloadAction")
+ .HasColumnType("INTEGER");
+
+ b.Property("DownloadManualFiles")
+ .HasColumnType("TEXT");
+
+ b.Property("DownloadMinSize")
+ .HasColumnType("INTEGER");
+
+ b.Property("FileOrMagnet")
+ .HasColumnType("TEXT");
+
+ b.Property("FinishedAction")
+ .HasColumnType("INTEGER");
+
+ b.Property("Hash")
+ .HasColumnType("TEXT");
+
+ b.Property("IsFile")
+ .HasColumnType("INTEGER");
+
+ b.Property("RdAdded")
+ .HasColumnType("TEXT");
+
+ b.Property("RdEnded")
+ .HasColumnType("TEXT");
+
+ b.Property("RdFiles")
+ .HasColumnType("TEXT");
+
+ b.Property("RdHost")
+ .HasColumnType("TEXT");
+
+ b.Property("RdId")
+ .HasColumnType("TEXT");
+
+ b.Property("RdName")
+ .HasColumnType("TEXT");
+
+ b.Property("RdProgress")
+ .HasColumnType("INTEGER");
+
+ b.Property("RdSeeders")
+ .HasColumnType("INTEGER");
+
+ b.Property("RdSize")
+ .HasColumnType("INTEGER");
+
+ b.Property("RdSpeed")
+ .HasColumnType("INTEGER");
+
+ b.Property("RdSplit")
+ .HasColumnType("INTEGER");
+
+ b.Property("RdStatus")
+ .HasColumnType("INTEGER");
+
+ b.Property("RdStatusRaw")
+ .HasColumnType("TEXT");
+
+ b.HasKey("TorrentId");
+
+ b.ToTable("Torrents");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", 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", 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
+ }
+ }
+}
diff --git a/server/RdtClient.Data/Migrations/20210718222602_Torrents_Add_Actions.cs b/server/RdtClient.Data/Migrations/20210718222602_Torrents_Add_Actions.cs
new file mode 100644
index 0000000..54218d4
--- /dev/null
+++ b/server/RdtClient.Data/Migrations/20210718222602_Torrents_Add_Actions.cs
@@ -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(
+ name: "FinishedAction",
+ table: "Torrents",
+ type: "INTEGER",
+ nullable: false,
+ defaultValue: 0);
+
+ migrationBuilder.AddColumn(
+ name: "DownloadAction",
+ table: "Torrents",
+ type: "INTEGER",
+ nullable: false,
+ defaultValue: 0);
+
+ migrationBuilder.AddColumn(
+ name: "DownloadManualFiles",
+ table: "Torrents",
+ type: "TEXT",
+ nullable: true);
+
+ migrationBuilder.AddColumn(
+ 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(
+ name: "AutoDelete",
+ table: "Torrents",
+ type: "INTEGER",
+ nullable: false,
+ defaultValue: 0);
+ }
+ }
+}
diff --git a/server/RdtClient.Data/Migrations/20210718231758_Torrents_Add_FilesSelected.Designer.cs b/server/RdtClient.Data/Migrations/20210718231758_Torrents_Add_FilesSelected.Designer.cs
new file mode 100644
index 0000000..2e26a27
--- /dev/null
+++ b/server/RdtClient.Data/Migrations/20210718231758_Torrents_Add_FilesSelected.Designer.cs
@@ -0,0 +1,433 @@
+//
+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("Id")
+ .HasColumnType("TEXT");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedName")
+ .IsUnique()
+ .HasDatabaseName("RoleNameIndex");
+
+ b.ToTable("AspNetRoles");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ClaimType")
+ .HasColumnType("TEXT");
+
+ b.Property("ClaimValue")
+ .HasColumnType("TEXT");
+
+ b.Property("RoleId")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetRoleClaims");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("TEXT");
+
+ b.Property("AccessFailedCount")
+ .HasColumnType("INTEGER");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("TEXT");
+
+ b.Property("Email")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("EmailConfirmed")
+ .HasColumnType("INTEGER");
+
+ b.Property("LockoutEnabled")
+ .HasColumnType("INTEGER");
+
+ b.Property("LockoutEnd")
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedEmail")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedUserName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("PasswordHash")
+ .HasColumnType("TEXT");
+
+ b.Property("PhoneNumber")
+ .HasColumnType("TEXT");
+
+ b.Property("PhoneNumberConfirmed")
+ .HasColumnType("INTEGER");
+
+ b.Property("SecurityStamp")
+ .HasColumnType("TEXT");
+
+ b.Property("TwoFactorEnabled")
+ .HasColumnType("INTEGER");
+
+ b.Property("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", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ClaimType")
+ .HasColumnType("TEXT");
+
+ b.Property("ClaimValue")
+ .HasColumnType("TEXT");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserClaims");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.Property("LoginProvider")
+ .HasColumnType("TEXT");
+
+ b.Property("ProviderKey")
+ .HasColumnType("TEXT");
+
+ b.Property("ProviderDisplayName")
+ .HasColumnType("TEXT");
+
+ b.Property("UserId")
+ .IsRequired()
+ .HasColumnType("TEXT");
+
+ b.HasKey("LoginProvider", "ProviderKey");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserLogins");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("TEXT");
+
+ b.Property("RoleId")
+ .HasColumnType("TEXT");
+
+ b.HasKey("UserId", "RoleId");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetUserRoles");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("TEXT");
+
+ b.Property("LoginProvider")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.Property("Value")
+ .HasColumnType("TEXT");
+
+ b.HasKey("UserId", "LoginProvider", "Name");
+
+ b.ToTable("AspNetUserTokens");
+ });
+
+ modelBuilder.Entity("RdtClient.Data.Models.Data.Download", b =>
+ {
+ b.Property("DownloadId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Added")
+ .HasColumnType("TEXT");
+
+ b.Property("Completed")
+ .HasColumnType("TEXT");
+
+ b.Property("DownloadFinished")
+ .HasColumnType("TEXT");
+
+ b.Property("DownloadQueued")
+ .HasColumnType("TEXT");
+
+ b.Property("DownloadStarted")
+ .HasColumnType("TEXT");
+
+ b.Property("Error")
+ .HasColumnType("TEXT");
+
+ b.Property("Link")
+ .HasColumnType("TEXT");
+
+ b.Property("Path")
+ .HasColumnType("TEXT");
+
+ b.Property("RetryCount")
+ .HasColumnType("INTEGER");
+
+ b.Property("TorrentId")
+ .HasColumnType("TEXT");
+
+ b.Property("UnpackingFinished")
+ .HasColumnType("TEXT");
+
+ b.Property("UnpackingQueued")
+ .HasColumnType("TEXT");
+
+ b.Property("UnpackingStarted")
+ .HasColumnType("TEXT");
+
+ b.HasKey("DownloadId");
+
+ b.HasIndex("TorrentId");
+
+ b.ToTable("Downloads");
+ });
+
+ modelBuilder.Entity("RdtClient.Data.Models.Data.Setting", b =>
+ {
+ b.Property("SettingId")
+ .HasColumnType("TEXT");
+
+ b.Property("Type")
+ .HasColumnType("TEXT");
+
+ b.Property("Value")
+ .HasColumnType("TEXT");
+
+ b.HasKey("SettingId");
+
+ b.ToTable("Settings");
+ });
+
+ modelBuilder.Entity("RdtClient.Data.Models.Data.Torrent", b =>
+ {
+ b.Property("TorrentId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Added")
+ .HasColumnType("TEXT");
+
+ b.Property("Category")
+ .HasColumnType("TEXT");
+
+ b.Property("Completed")
+ .HasColumnType("TEXT");
+
+ b.Property("DownloadAction")
+ .HasColumnType("INTEGER");
+
+ b.Property("DownloadManualFiles")
+ .HasColumnType("TEXT");
+
+ b.Property("DownloadMinSize")
+ .HasColumnType("INTEGER");
+
+ b.Property("FileOrMagnet")
+ .HasColumnType("TEXT");
+
+ b.Property("FilesSelected")
+ .HasColumnType("TEXT");
+
+ b.Property("FinishedAction")
+ .HasColumnType("INTEGER");
+
+ b.Property("Hash")
+ .HasColumnType("TEXT");
+
+ b.Property("IsFile")
+ .HasColumnType("INTEGER");
+
+ b.Property("RdAdded")
+ .HasColumnType("TEXT");
+
+ b.Property("RdEnded")
+ .HasColumnType("TEXT");
+
+ b.Property("RdFiles")
+ .HasColumnType("TEXT");
+
+ b.Property("RdHost")
+ .HasColumnType("TEXT");
+
+ b.Property("RdId")
+ .HasColumnType("TEXT");
+
+ b.Property("RdName")
+ .HasColumnType("TEXT");
+
+ b.Property("RdProgress")
+ .HasColumnType("INTEGER");
+
+ b.Property("RdSeeders")
+ .HasColumnType("INTEGER");
+
+ b.Property("RdSize")
+ .HasColumnType("INTEGER");
+
+ b.Property("RdSpeed")
+ .HasColumnType("INTEGER");
+
+ b.Property("RdSplit")
+ .HasColumnType("INTEGER");
+
+ b.Property("RdStatus")
+ .HasColumnType("INTEGER");
+
+ b.Property("RdStatusRaw")
+ .HasColumnType("TEXT");
+
+ b.HasKey("TorrentId");
+
+ b.ToTable("Torrents");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", 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", 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
+ }
+ }
+}
diff --git a/server/RdtClient.Data/Migrations/20210718231758_Torrents_Add_FilesSelected.cs b/server/RdtClient.Data/Migrations/20210718231758_Torrents_Add_FilesSelected.cs
new file mode 100644
index 0000000..fde01aa
--- /dev/null
+++ b/server/RdtClient.Data/Migrations/20210718231758_Torrents_Add_FilesSelected.cs
@@ -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(
+ name: "FilesSelected",
+ table: "Torrents",
+ type: "TEXT",
+ nullable: true);
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "FilesSelected",
+ table: "Torrents");
+ }
+ }
+}
diff --git a/server/RdtClient.Data/Migrations/DataContextModelSnapshot.cs b/server/RdtClient.Data/Migrations/DataContextModelSnapshot.cs
index b614ada..9cc3b17 100644
--- a/server/RdtClient.Data/Migrations/DataContextModelSnapshot.cs
+++ b/server/RdtClient.Data/Migrations/DataContextModelSnapshot.cs
@@ -14,7 +14,7 @@ namespace RdtClient.Data.Migrations
{
#pragma warning disable 612, 618
modelBuilder
- .HasAnnotation("ProductVersion", "5.0.3");
+ .HasAnnotation("ProductVersion", "5.0.8");
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
@@ -285,18 +285,30 @@ namespace RdtClient.Data.Migrations
b.Property("Added")
.HasColumnType("TEXT");
- b.Property("AutoDelete")
- .HasColumnType("INTEGER");
-
b.Property("Category")
.HasColumnType("TEXT");
b.Property("Completed")
.HasColumnType("TEXT");
+ b.Property("DownloadAction")
+ .HasColumnType("INTEGER");
+
+ b.Property("DownloadManualFiles")
+ .HasColumnType("TEXT");
+
+ b.Property("DownloadMinSize")
+ .HasColumnType("INTEGER");
+
b.Property("FileOrMagnet")
.HasColumnType("TEXT");
+ b.Property("FilesSelected")
+ .HasColumnType("TEXT");
+
+ b.Property("FinishedAction")
+ .HasColumnType("INTEGER");
+
b.Property("Hash")
.HasColumnType("TEXT");
diff --git a/server/RdtClient.Data/Models/Data/Torrent.cs b/server/RdtClient.Data/Models/Data/Torrent.cs
index 165c7a9..2a0466e 100644
--- a/server/RdtClient.Data/Models/Data/Torrent.cs
+++ b/server/RdtClient.Data/Models/Data/Torrent.cs
@@ -17,17 +17,15 @@ namespace RdtClient.Data.Models.Data
public String Category { get; set; }
+ public TorrentDownloadAction DownloadAction { get; set; }
+ public TorrentFinishedAction FinishedAction { get; set; }
+ public Int32 DownloadMinSize { get; set; }
+ public String DownloadManualFiles { get; set; }
+
public DateTimeOffset Added { get; set; }
+ public DateTimeOffset? FilesSelected { get; set; }
public DateTimeOffset? Completed { get; set; }
- public Boolean DownloadOnlyAvailableFiles { get; set; }
-
- public String DownloadFiles { get; set; }
-
- public Int32 MinimumFileSize { get; set; }
-
- public Boolean AutoDelete { get; set; }
-
public String FileOrMagnet { get; set; }
public Boolean IsFile { get; set; }
@@ -68,5 +66,19 @@ namespace RdtClient.Data.Models.Data
}
}
}
+
+ [NotMapped]
+ public IList ManualFiles
+ {
+ get
+ {
+ if (String.IsNullOrWhiteSpace(DownloadManualFiles))
+ {
+ return new List();
+ }
+
+ return DownloadManualFiles.Split(",");
+ }
+ }
}
}
diff --git a/server/RdtClient.Service/Services/QBittorrent.cs b/server/RdtClient.Service/Services/QBittorrent.cs
index 1af7223..c554759 100644
--- a/server/RdtClient.Service/Services/QBittorrent.cs
+++ b/server/RdtClient.Service/Services/QBittorrent.cs
@@ -19,8 +19,8 @@ namespace RdtClient.Service.Services
Task> TorrentFileContents(String hash);
Task TorrentProperties(String hash);
Task TorrentsDelete(String hash, Boolean deleteFiles);
- Task TorrentsAddMagnet(String magnetLink, String category, Boolean autoDelete);
- Task TorrentsAddFile(Byte[] fileBytes, String category, Boolean autoDelete);
+ Task TorrentsAddMagnet(String magnetLink, String category);
+ Task TorrentsAddFile(Byte[] fileBytes, String category);
Task TorrentsSetCategory(String hash, String category);
Task> TorrentsCategories();
Task CategoryCreate(String category);
@@ -432,14 +432,24 @@ namespace RdtClient.Service.Services
await _torrents.Delete(torrent.TorrentId, true, true, deleteFiles);
}
- public async Task TorrentsAddMagnet(String magnetLink, String category, Boolean autoDelete)
+ public async Task TorrentsAddMagnet(String magnetLink, String category)
{
- await _torrents.UploadMagnet(magnetLink, category, autoDelete);
+ var onlyDownloadAvailableFilesSetting = await _settings.GetNumber("OnlyDownloadAvailableFiles");
+ var minFileSizeSetting = await _settings.GetNumber("MinFileSize");
+
+ var downloadAction = onlyDownloadAvailableFilesSetting == 1 ? TorrentDownloadAction.DownloadAvailableFiles : TorrentDownloadAction.DownloadAll;
+
+ await _torrents.UploadMagnet(magnetLink, category, downloadAction, TorrentFinishedAction.None, minFileSizeSetting, null);
}
- public async Task TorrentsAddFile(Byte[] fileBytes, String category, Boolean autoDelete)
+ public async Task TorrentsAddFile(Byte[] fileBytes, String category)
{
- await _torrents.UploadFile(fileBytes, category, autoDelete);
+ var onlyDownloadAvailableFilesSetting = await _settings.GetNumber("OnlyDownloadAvailableFiles");
+ var minFileSizeSetting = await _settings.GetNumber("MinFileSize");
+
+ var downloadAction = onlyDownloadAvailableFilesSetting == 1 ? TorrentDownloadAction.DownloadAvailableFiles : TorrentDownloadAction.DownloadAll;
+
+ await _torrents.UploadFile(fileBytes, category, downloadAction, TorrentFinishedAction.None, minFileSizeSetting, null);
}
public async Task TorrentsSetCategory(String hash, String category)
diff --git a/server/RdtClient.Service/Services/TorrentRunner.cs b/server/RdtClient.Service/Services/TorrentRunner.cs
index 762b546..3340ad7 100644
--- a/server/RdtClient.Service/Services/TorrentRunner.cs
+++ b/server/RdtClient.Service/Services/TorrentRunner.cs
@@ -80,18 +80,7 @@ namespace RdtClient.Service.Services
Log.Debug($"No RealDebridApiKey set!");
return;
}
-
- var settingMinFileSize = settings.GetNumber("MinFileSize");
- if (settingMinFileSize <= 0)
- {
- settingMinFileSize = 0;
- }
-
- settingMinFileSize = settingMinFileSize * 1024 * 1024;
-
- var settingOnlyDownloadAvailableFilesRaw = settings.GetNumber("OnlyDownloadAvailableFiles");
- var settingOnlyDownloadAvailableFiles = settingOnlyDownloadAvailableFilesRaw == 1;
-
+
var settingDownloadLimit = settings.GetNumber("DownloadLimit");
if (settingDownloadLimit < 1)
{
@@ -365,35 +354,52 @@ namespace RdtClient.Service.Services
continue;
}
+ // The files are selected but there are no downloads yet, check if Real Debrid has generated links yet.
+ if (torrent.Downloads.Count == 0 && torrent.FilesSelected != null)
+ {
+ await _torrents.CheckForLinks(torrent.TorrentId);
+ }
+
// RealDebrid is waiting for file selection, select which files to download.
if ((torrent.RdStatus == RealDebridStatus.WaitingForFileSelection || torrent.RdStatus == RealDebridStatus.Finished) &&
- torrent.Downloads.Count == 0)
+ torrent.Downloads.Count == 0 &&
+ torrent.FilesSelected == null)
{
Log.Debug($"Torrent {torrent.RdId} selecting files");
var files = torrent.Files;
- if (settingOnlyDownloadAvailableFiles)
+ if (torrent.DownloadAction == TorrentDownloadAction.DownloadAvailableFiles)
{
Log.Debug($"Torrent {torrent.RdId} determining which files are already available");
var availableFiles = await _torrents.GetAvailableFiles(torrent.Hash);
- Log.Debug($"Found {availableFiles.Count} available files for torrent {torrent.RdId}");
+ Log.Debug($"Found {files.Count}/{torrent.Files.Count} available files for torrent {torrent.RdId}");
- if (availableFiles.Count > 0)
- {
- files = torrent.Files.Where(m => availableFiles.Any(f => m.Path.EndsWith(f))).ToList();
-
- Log.Debug($"Selecting {files.Count} available files for torrent {torrent.RdId}");
- }
+ files = torrent.Files.Where(m => availableFiles.Any(f => m.Path.EndsWith(f.Filename))).ToList();
+ }
+ else if (torrent.DownloadAction == TorrentDownloadAction.DownloadAll)
+ {
+ Log.Debug("Selecting all files");
+ files = torrent.Files.ToList();
+ }
+ else if (torrent.DownloadAction == TorrentDownloadAction.DownloadManual)
+ {
+ Log.Debug("Selecting manual selected files");
+ files = torrent.Files.Where(m => torrent.ManualFiles.Any(f => m.Path.EndsWith(f))).ToList();
}
- if (settingMinFileSize > 0)
- {
- Log.Debug($"Torrent {torrent.RdId} determining which files are over {settingMinFileSize} bytes");
+ Log.Debug($"Selecting {files.Count}/{torrent.Files.Count} files for torrent {torrent.RdId}");
- files = files.Where(m => m.Bytes > settingMinFileSize).ToList();
+ if (torrent.DownloadAction != TorrentDownloadAction.DownloadManual && torrent.DownloadMinSize > 0)
+ {
+ var minFileSize = torrent.DownloadMinSize * 1024 * 1024;
+
+ Log.Debug($"Torrent {torrent.RdId} determining which files are over {minFileSize} bytes");
+
+ files = files.Where(m => m.Bytes > minFileSize)
+ .ToList();
Log.Debug($"Found {files.Count} files that match the minimum file size criterea for torrent {torrent.RdId}");
}
@@ -410,13 +416,8 @@ namespace RdtClient.Service.Services
Log.Debug($"Selecting files for torrent {torrent.RdId}: {String.Join(", ", fileIds)}");
await _torrents.SelectFiles(torrent.TorrentId, fileIds);
- }
- // If the torrent doesn't have any files at this point, don't process it further.
- if (torrent.Files.Count == 0)
- {
- Log.Debug($"No files found for torrent {torrent.RdId}!");
- continue;
+ await _torrents.UpdateFilesSelected(torrent.TorrentId, DateTime.UtcNow);
}
// RealDebrid finished downloading the torrent, process the file to host.
@@ -479,12 +480,19 @@ namespace RdtClient.Service.Services
await _torrents.UpdateComplete(torrent.TorrentId, DateTimeOffset.UtcNow);
- // Remove the torrent from RealDebrid
- if (torrent.AutoDelete)
+ switch (torrent.FinishedAction)
{
- Log.Debug($"Torrent {torrent.RdId} removing");
-
- await _torrents.Delete(torrent.TorrentId, true, true, false);
+ case TorrentFinishedAction.RemoveAllTorrents:
+ Log.Debug($"Torrent {torrent.RdId} removing torrents from Real Debrid and Real Debrid Client, no files");
+ await _torrents.Delete(torrent.TorrentId, true, true, false);
+ break;
+ case TorrentFinishedAction.RemoveRealDebrid:
+ Log.Debug($"Torrent {torrent.RdId} removing torrents from Real Debrid, no files");
+ await _torrents.Delete(torrent.TorrentId, false, true, false);
+ break;
+ case TorrentFinishedAction.None:
+ Log.Debug($"Torrent {torrent.RdId} not removing torrents or files");
+ break;
}
}
}
diff --git a/server/RdtClient.Service/Services/Torrents.cs b/server/RdtClient.Service/Services/Torrents.cs
index deb90c5..c0aefdb 100644
--- a/server/RdtClient.Service/Services/Torrents.cs
+++ b/server/RdtClient.Service/Services/Torrents.cs
@@ -7,9 +7,9 @@ using System.Threading.Tasks;
using MonoTorrent;
using Newtonsoft.Json;
using RDNET;
+using RdtClient.Data.Data;
using RdtClient.Data.Enums;
using RdtClient.Service.Models;
-using ITorrentData = RdtClient.Data.Data.ITorrentData;
using Torrent = RdtClient.Data.Models.Data.Torrent;
namespace RdtClient.Service.Services
@@ -19,10 +19,24 @@ namespace RdtClient.Service.Services
Task> Get();
Task GetByHash(String hash);
Task UpdateCategory(String hash, String category);
- Task UploadMagnet(String magnetLink, String category, Boolean autoDelete);
- Task UploadFile(Byte[] bytes, String category, Boolean autoDelete);
- Task> GetAvailableFiles(String hash);
+
+ Task UploadMagnet(String magnetLink,
+ String category,
+ TorrentDownloadAction downloadAction,
+ TorrentFinishedAction finishedAction,
+ Int32 downloadMinSize,
+ String downloadManualFiles);
+
+ Task UploadFile(Byte[] bytes,
+ String category,
+ TorrentDownloadAction downloadAction,
+ TorrentFinishedAction finishedAction,
+ Int32 downloadMinSize,
+ String downloadManualFiles);
+
+ Task> GetAvailableFiles(String hash);
Task SelectFiles(Guid torrentId, IList fileIds);
+ Task CheckForLinks(Guid torrentId);
Task Delete(Guid torrentId, Boolean deleteData, Boolean deleteRdTorrent, Boolean deleteLocalFiles);
Task UnrestrictLink(Guid downloadId);
Task Download(Guid downloadId);
@@ -30,6 +44,7 @@ namespace RdtClient.Service.Services
void Reset();
Task GetProfile();
Task UpdateComplete(Guid torrentId, DateTimeOffset datetime);
+ Task UpdateFilesSelected(Guid torrentId, DateTimeOffset datetime);
Task Update();
Task Retry(Guid id, Int32 retry);
}
@@ -39,7 +54,7 @@ namespace RdtClient.Service.Services
private static RdNetClient _rdtNetClient;
private static readonly SemaphoreSlim SemaphoreSlim = new(1, 1);
-
+
private readonly IDownloads _downloads;
private readonly ISettings _settings;
private readonly ITorrentData _torrentData;
@@ -51,28 +66,10 @@ namespace RdtClient.Service.Services
_downloads = downloads;
}
- private RdNetClient GetRdNetClient()
- {
- if (_rdtNetClient == null)
- {
- var apiKey = _settings.GetString("RealDebridApiKey")
- .Result;
-
- if (String.IsNullOrWhiteSpace(apiKey))
- {
- throw new Exception("RealDebrid API Key not set in the settings");
- }
-
- _rdtNetClient = new RdNetClient("X245A4XAIBGVM", null, null, null, apiKey);
- }
-
- return _rdtNetClient;
- }
-
public async Task> Get()
{
var torrents = await _torrentData.Get();
-
+
foreach (var torrent in torrents)
{
foreach (var download in torrent.Downloads)
@@ -83,7 +80,7 @@ namespace RdtClient.Service.Services
download.BytesTotal = downloadClient.BytesTotal;
download.BytesDone = downloadClient.BytesDone;
}
-
+
if (TorrentRunner.ActiveUnpackClients.TryGetValue(download.DownloadId, out var unpackClient))
{
download.BytesTotal = unpackClient.BytesTotal;
@@ -95,18 +92,6 @@ namespace RdtClient.Service.Services
return torrents;
}
- public async Task GetById(Guid torrentId)
- {
- var torrent = await _torrentData.GetById(torrentId);
-
- if (torrent != null)
- {
- await Update(torrent);
- }
-
- return torrent;
- }
-
public async Task GetByHash(String hash)
{
var torrent = await _torrentData.GetByHash(hash);
@@ -131,7 +116,12 @@ namespace RdtClient.Service.Services
await _torrentData.UpdateCategory(torrent.TorrentId, category);
}
- public async Task UploadMagnet(String magnetLink, String category, Boolean autoDelete)
+ public async Task UploadMagnet(String magnetLink,
+ String category,
+ TorrentDownloadAction downloadAction,
+ TorrentFinishedAction finishedAction,
+ Int32 downloadMinSize,
+ String downloadManualFiles)
{
MagnetLink magnet;
@@ -146,10 +136,15 @@ namespace RdtClient.Service.Services
var rdTorrent = await GetRdNetClient().AddTorrentMagnetAsync(magnetLink);
- await Add(rdTorrent.Id, magnet.InfoHash.ToHex(), category, autoDelete, magnetLink, false);
+ await Add(rdTorrent.Id, magnet.InfoHash.ToHex(), category, downloadAction, finishedAction, downloadMinSize, downloadManualFiles, magnetLink, false);
}
- public async Task UploadFile(Byte[] bytes, String category, Boolean autoDelete)
+ public async Task UploadFile(Byte[] bytes,
+ String category,
+ TorrentDownloadAction downloadAction,
+ TorrentFinishedAction finishedAction,
+ Int32 downloadMinSize,
+ String downloadManualFiles)
{
MonoTorrent.Torrent torrent;
@@ -165,44 +160,36 @@ namespace RdtClient.Service.Services
}
var rdTorrent = await GetRdNetClient().AddTorrentFileAsync(bytes);
-
- await Add(rdTorrent.Id, torrent.InfoHash.ToHex(), category, autoDelete, fileAsBase64, true);
+
+ await Add(rdTorrent.Id, torrent.InfoHash.ToHex(), category, downloadAction, finishedAction, downloadMinSize, downloadManualFiles, fileAsBase64, true);
}
- public async Task> GetAvailableFiles(String hash)
+ public async Task> GetAvailableFiles(String hash)
{
var result = await GetRdNetClient().GetAvailableFiles(hash);
var files = result.SelectMany(m => m.Value).SelectMany(m => m.Value).SelectMany(m => m.Values);
- var groups = files.GroupBy(m => m.Filename);
+ var groups = files.GroupBy(m => $"{m.Filename}-{m.Filesize}");
- return groups.Select(m => m.Key).ToList();
+ return groups.Select(m => m.First()).ToList();
}
public async Task SelectFiles(Guid torrentId, IList fileIds)
{
var torrent = await GetById(torrentId);
- RDNET.Torrent rdTorrent = null;
+ await GetRdNetClient().SelectTorrentFilesAsync(torrent.RdId, fileIds.ToArray());
+ }
- for (var i = 0; i < 5; i++)
- {
- await GetRdNetClient().SelectTorrentFilesAsync(torrent.RdId, fileIds.ToArray());
+ public async Task CheckForLinks(Guid torrentId)
+ {
+ var torrent = await GetById(torrentId);
- await Task.Delay(5000);
+ var rdTorrent = await GetRdNetClient().GetTorrentInfoAsync(torrent.RdId);
- rdTorrent = await GetRdNetClient().GetTorrentInfoAsync(torrent.RdId);
-
- if (fileIds.Count == rdTorrent.Links.Count)
- {
- break;
- }
-
- await Task.Delay(10000);
- }
-
- if (rdTorrent == null)
+ // Sometimes RD will give you 1 rar with all files, sometimes it will give you 1 link per file.
+ if (torrent.Files.Count != rdTorrent.Links.Count && rdTorrent.Links.Count != 1)
{
return;
}
@@ -254,7 +241,7 @@ namespace RdtClient.Service.Services
{
var downloadPath = await DownloadPath(torrent);
downloadPath = Path.Combine(downloadPath, torrent.RdName);
-
+
if (Directory.Exists(downloadPath))
{
var retry = 0;
@@ -270,6 +257,7 @@ namespace RdtClient.Service.Services
catch
{
retry++;
+
if (retry >= 3)
{
throw;
@@ -332,33 +320,6 @@ namespace RdtClient.Service.Services
return profile;
}
- private async Task Add(String rdTorrentId, String infoHash, String category, Boolean autoDelete, String fileOrMagnetContents, Boolean isFile)
- {
- var w = await SemaphoreSlim.WaitAsync(60000);
- if (!w)
- {
- throw new Exception("Unable to add torrent, could not obtain lock");
- }
-
- try
- {
- var torrent = await _torrentData.GetByHash(infoHash);
-
- if (torrent != null)
- {
- return;
- }
-
- var newTorrent = await _torrentData.Add(rdTorrentId, infoHash, category, autoDelete, fileOrMagnetContents, isFile);
-
- await Update(newTorrent);
- }
- finally
- {
- SemaphoreSlim.Release();
- }
- }
-
public async Task Update()
{
var w = await SemaphoreSlim.WaitAsync(1);
@@ -382,7 +343,7 @@ namespace RdtClient.Service.Services
{
continue;
}
-
+
await Update(torrent);
}
@@ -420,11 +381,16 @@ namespace RdtClient.Service.Services
{
var bytes = Convert.FromBase64String(torrent.FileOrMagnet);
- await UploadFile(bytes, torrent.Category, torrent.AutoDelete);
+ await UploadFile(bytes, torrent.Category, torrent.DownloadAction, torrent.FinishedAction, torrent.DownloadMinSize, torrent.DownloadManualFiles);
}
else
{
- await UploadMagnet(torrent.FileOrMagnet, torrent.Category, torrent.AutoDelete);
+ await UploadMagnet(torrent.FileOrMagnet,
+ torrent.Category,
+ torrent.DownloadAction,
+ torrent.FinishedAction,
+ torrent.DownloadMinSize,
+ torrent.DownloadManualFiles);
}
}
else if (retry == 1)
@@ -445,10 +411,89 @@ namespace RdtClient.Service.Services
await _torrentData.UpdateComplete(torrentId, datetime);
}
+ public async Task UpdateFilesSelected(Guid torrentId, DateTimeOffset datetime)
+ {
+ await _torrentData.UpdateFilesSelected(torrentId, datetime);
+ }
+
+ private RdNetClient GetRdNetClient()
+ {
+ if (_rdtNetClient == null)
+ {
+ var apiKey = _settings.GetString("RealDebridApiKey")
+ .Result;
+
+ if (String.IsNullOrWhiteSpace(apiKey))
+ {
+ throw new Exception("RealDebrid API Key not set in the settings");
+ }
+
+ _rdtNetClient = new RdNetClient("X245A4XAIBGVM", null, null, null, apiKey);
+ }
+
+ return _rdtNetClient;
+ }
+
+ public async Task 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 DownloadPath(Torrent torrent)
{
var settingDownloadPath = await _settings.GetString("DownloadPath");
-
+
if (!String.IsNullOrWhiteSpace(torrent.Category))
{
settingDownloadPath = Path.Combine(settingDownloadPath, torrent.Category);
@@ -459,10 +504,11 @@ namespace RdtClient.Service.Services
private async Task Update(Torrent torrent)
{
- var originalTorrent = JsonConvert.SerializeObject(torrent, new JsonSerializerSettings
- {
- ReferenceLoopHandling = ReferenceLoopHandling.Ignore
- });
+ var originalTorrent = JsonConvert.SerializeObject(torrent,
+ new JsonSerializerSettings
+ {
+ ReferenceLoopHandling = ReferenceLoopHandling.Ignore
+ });
var rdTorrent = await GetRdNetClient().GetTorrentInfoAsync(torrent.RdId);
@@ -515,10 +561,11 @@ namespace RdtClient.Service.Services
_ => RealDebridStatus.Error
};
- var newTorrent = JsonConvert.SerializeObject(torrent, new JsonSerializerSettings
- {
- ReferenceLoopHandling = ReferenceLoopHandling.Ignore
- });
+ var newTorrent = JsonConvert.SerializeObject(torrent,
+ new JsonSerializerSettings
+ {
+ ReferenceLoopHandling = ReferenceLoopHandling.Ignore
+ });
if (originalTorrent != newTorrent)
{
@@ -526,4 +573,4 @@ namespace RdtClient.Service.Services
}
}
}
-}
\ No newline at end of file
+}
diff --git a/server/RdtClient.Web/Controllers/QBittorrentController.cs b/server/RdtClient.Web/Controllers/QBittorrentController.cs
index 7b63f96..377fc14 100644
--- a/server/RdtClient.Web/Controllers/QBittorrentController.cs
+++ b/server/RdtClient.Web/Controllers/QBittorrentController.cs
@@ -248,13 +248,13 @@ namespace RdtClient.Web.Controllers
{
if (url.StartsWith("magnet"))
{
- await _qBittorrent.TorrentsAddMagnet(url.Trim(), request.Category, false);
+ await _qBittorrent.TorrentsAddMagnet(url.Trim(), request.Category);
}
else if (url.StartsWith("http"))
{
var httpClient = new HttpClient();
var result = await httpClient.GetByteArrayAsync(url);
- await _qBittorrent.TorrentsAddFile(result, request.Category, false);
+ await _qBittorrent.TorrentsAddFile(result, request.Category);
}
else
{
@@ -279,7 +279,7 @@ namespace RdtClient.Web.Controllers
await file.CopyToAsync(target);
var fileBytes = target.ToArray();
- await _qBittorrent.TorrentsAddFile(fileBytes, request.Category, false);
+ await _qBittorrent.TorrentsAddFile(fileBytes, request.Category);
}
}
diff --git a/server/RdtClient.Web/Controllers/TorrentsController.cs b/server/RdtClient.Web/Controllers/TorrentsController.cs
index 1ac28d9..d28b0e0 100644
--- a/server/RdtClient.Web/Controllers/TorrentsController.cs
+++ b/server/RdtClient.Web/Controllers/TorrentsController.cs
@@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using MonoTorrent;
+using RdtClient.Data.Enums;
using RdtClient.Service.Helpers;
using RdtClient.Service.Services;
using Torrent = RdtClient.Data.Models.Data.Torrent;
@@ -17,8 +18,8 @@ namespace RdtClient.Web.Controllers
[Route("Api/Torrents")]
public class TorrentsController : Controller
{
- private readonly ITorrents _torrents;
private readonly ITorrentRunner _torrentRunner;
+ private readonly ITorrents _torrents;
public TorrentsController(ITorrents torrents, ITorrentRunner torrentRunner)
{
@@ -31,18 +32,18 @@ namespace RdtClient.Web.Controllers
public async Task>> Get()
{
var results = await _torrents.Get();
-
+
// Prevent infinite recursion when serializing
foreach (var file in results.SelectMany(torrent => torrent.Downloads))
{
file.Torrent = null;
}
-
+
return Ok(results);
}
///
- /// Used for debugging only. Force a tick.
+ /// Used for debugging only. Force a tick.
///
///
[HttpGet]
@@ -73,7 +74,7 @@ namespace RdtClient.Web.Controllers
var bytes = memoryStream.ToArray();
- await _torrents.UploadFile(bytes, null, formData.AutoDelete);
+ await _torrents.UploadFile(bytes, formData.Category, formData.DownloadAction, formData.FinishedAction, formData.DownloadMinSize, formData.DownloadManualFiles);
return Ok();
}
@@ -82,11 +83,16 @@ namespace RdtClient.Web.Controllers
[Route("UploadMagnet")]
public async Task UploadMagnet([FromBody] TorrentControllerUploadMagnetRequest request)
{
- await _torrents.UploadMagnet(request.MagnetLink, null, request.AutoDelete);
+ await _torrents.UploadMagnet(request.MagnetLink,
+ request.Category,
+ request.DownloadAction,
+ request.FinishedAction,
+ request.DownloadMinSize,
+ request.DownloadManualFiles);
return Ok();
}
-
+
[HttpPost]
[Route("CheckFiles")]
public async Task CheckFiles([FromForm] IFormFile file)
@@ -130,7 +136,7 @@ namespace RdtClient.Web.Controllers
return Ok();
}
-
+
[HttpPost]
[Route("Retry/{id}")]
public async Task Retry(Guid id, [FromBody] TorrentControllerRetryRequest request)
@@ -143,13 +149,21 @@ namespace RdtClient.Web.Controllers
public class TorrentControllerUploadFileRequest
{
- public Boolean AutoDelete { get; set; }
+ public String Category { get; set; }
+ public TorrentDownloadAction DownloadAction { get; set; }
+ public TorrentFinishedAction FinishedAction { get; set; }
+ public Int32 DownloadMinSize { get; set; }
+ public String DownloadManualFiles { get; set; }
}
public class TorrentControllerUploadMagnetRequest
{
public String MagnetLink { get; set; }
- public Boolean AutoDelete { get; set; }
+ public String Category { get; set; }
+ public TorrentDownloadAction DownloadAction { get; set; }
+ public TorrentFinishedAction FinishedAction { get; set; }
+ public Int32 DownloadMinSize { get; set; }
+ public String DownloadManualFiles { get; set; }
}
public class TorrentControllerDeleteRequest
@@ -168,4 +182,4 @@ namespace RdtClient.Web.Controllers
{
public String MagnetLink { get; set; }
}
-}
\ No newline at end of file
+}