Created a separate page for torrent into, added download retrying per file.
This commit is contained in:
parent
fa11dc9bc7
commit
3ee687f615
50 changed files with 1101 additions and 661 deletions
5
client/package-lock.json
generated
5
client/package-lock.json
generated
|
|
@ -5165,6 +5165,11 @@
|
|||
"escape-string-regexp": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"file-saver": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
|
||||
"integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
|
||||
},
|
||||
"filesize": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/filesize/-/filesize-6.1.0.tgz",
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
"@microsoft/signalr": "^5.0.8",
|
||||
"bulma": "^0.9.3",
|
||||
"curray": "^1.0.8",
|
||||
"file-saver": "^2.0.5",
|
||||
"ngx-filesize": "^2.0.16",
|
||||
"rxjs": "~7.2.0",
|
||||
"tslib": "^2.3.0",
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@
|
|||
<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]="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>
|
||||
|
|
@ -66,8 +66,8 @@
|
|||
<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>
|
||||
<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>
|
||||
|
|
@ -84,10 +84,16 @@
|
|||
<div class="field">
|
||||
<label class="label">Available files</label>
|
||||
<p class="help">
|
||||
These files are available for immediate download from Real Debrid. <br />
|
||||
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">
|
||||
<input type="checkbox" [checked]="allSelected" (change)="downloadFileCheckedAll()" />
|
||||
Select all
|
||||
</label>
|
||||
</div>
|
||||
<div class="field" *ngIf="downloadAction === 2">
|
||||
<label class="checkbox is-fullwidth-label" *ngFor="let file of availableFiles">
|
||||
<input
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export class AddNewTorrentComponent implements OnInit {
|
|||
|
||||
public availableFiles: TorrentFileAvailability[] = [];
|
||||
public downloadFiles: { [key: string]: boolean } = {};
|
||||
public allSelected: boolean;
|
||||
|
||||
public saving = false;
|
||||
public error: string;
|
||||
|
|
@ -50,6 +51,21 @@ export class AddNewTorrentComponent implements OnInit {
|
|||
|
||||
public downloadFileChecked(file: string): void {
|
||||
this.downloadFiles[file] = !this.downloadFiles[file];
|
||||
|
||||
this.allSelected = true;
|
||||
this.availableFiles.forEach((file) => {
|
||||
if (!this.downloadFiles[file.filename]) {
|
||||
this.allSelected = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public downloadFileCheckedAll(): void {
|
||||
this.allSelected = !this.allSelected;
|
||||
|
||||
this.availableFiles.forEach((file) => {
|
||||
this.downloadFiles[file.filename] = this.allSelected;
|
||||
});
|
||||
}
|
||||
|
||||
public ok(): void {
|
||||
|
|
@ -127,6 +143,7 @@ export class AddNewTorrentComponent implements OnInit {
|
|||
this.error = null;
|
||||
this.availableFiles = [];
|
||||
this.downloadFiles = {};
|
||||
this.allSelected = true;
|
||||
|
||||
if (this.magnetLink) {
|
||||
this.torrentService.checkFilesMagnet(this.magnetLink).subscribe(
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ 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';
|
||||
import { TorrentComponent } from './torrent/torrent.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
|
|
@ -28,6 +29,10 @@ const routes: Routes = [
|
|||
path: '',
|
||||
component: TorrentTableComponent,
|
||||
},
|
||||
{
|
||||
path: 'torrent/:id',
|
||||
component: TorrentComponent,
|
||||
},
|
||||
{
|
||||
path: 'add',
|
||||
component: AddNewTorrentComponent,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { ClipboardModule } from '@angular/cdk/clipboard';
|
||||
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FlexLayoutModule } from '@angular/flex-layout';
|
||||
|
|
@ -15,11 +16,10 @@ import { MainLayoutComponent } from './main-layout/main-layout.component';
|
|||
import { NavbarComponent } from './navbar/navbar.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';
|
||||
import { TorrentRowComponent } from './torrent-row/torrent-row.component';
|
||||
import { TorrentStatusPipe } from './torrent-status.pipe';
|
||||
import { TorrentTableComponent } from './torrent-table/torrent-table.component';
|
||||
import { TorrentComponent } from './torrent/torrent.component';
|
||||
import { DecodeURIPipe } from './decode-uri.pipe';
|
||||
|
||||
curray();
|
||||
|
||||
|
|
@ -30,16 +30,23 @@ curray();
|
|||
NavbarComponent,
|
||||
AddNewTorrentComponent,
|
||||
TorrentTableComponent,
|
||||
TorrentRowComponent,
|
||||
TorrentFileComponent,
|
||||
SettingsComponent,
|
||||
TorrentStatusPipe,
|
||||
DownloadStatusPipe,
|
||||
LoginComponent,
|
||||
SetupComponent,
|
||||
TorrentDownloadComponent,
|
||||
TorrentComponent,
|
||||
DecodeURIPipe,
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
AppRoutingModule,
|
||||
FormsModule,
|
||||
HttpClientModule,
|
||||
NgxFilesizeModule,
|
||||
FlexLayoutModule,
|
||||
ClipboardModule,
|
||||
],
|
||||
imports: [BrowserModule, AppRoutingModule, FormsModule, HttpClientModule, NgxFilesizeModule, FlexLayoutModule],
|
||||
providers: [FileSizePipe, { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
|
|
|
|||
10
client/src/app/decode-uri.pipe.ts
Normal file
10
client/src/app/decode-uri.pipe.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
@Pipe({
|
||||
name: 'decodeURI',
|
||||
})
|
||||
export class DecodeURIPipe implements PipeTransform {
|
||||
transform(input: any) {
|
||||
return decodeURI(input);
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ export class DownloadStatusPipe implements PipeTransform {
|
|||
}
|
||||
|
||||
if (value.error) {
|
||||
return `Error: ${value.error}`;
|
||||
return `Error`;
|
||||
}
|
||||
|
||||
if (value.completed != null) {
|
||||
|
|
@ -26,8 +26,13 @@ export class DownloadStatusPipe implements PipeTransform {
|
|||
}
|
||||
|
||||
if (value.unpackingStarted) {
|
||||
const progress = ((value.bytesDone / value.bytesTotal) * 100).toFixed(2);
|
||||
return `Unpacking ${progress || 0}%`;
|
||||
let progress = (value.bytesDone / value.bytesTotal) * 100;
|
||||
|
||||
if (isNaN(progress)) {
|
||||
progress = 0;
|
||||
}
|
||||
|
||||
return `Unpacking ${progress.toFixed(2)}%`;
|
||||
}
|
||||
|
||||
if (value.unpackingQueued) {
|
||||
|
|
@ -39,10 +44,15 @@ export class DownloadStatusPipe implements PipeTransform {
|
|||
}
|
||||
|
||||
if (value.downloadStarted) {
|
||||
const progress = ((value.bytesDone / value.bytesTotal) * 100).toFixed(2);
|
||||
let progress = (value.bytesDone / value.bytesTotal) * 100;
|
||||
|
||||
if (isNaN(progress)) {
|
||||
progress = 0;
|
||||
}
|
||||
|
||||
const speed = this.pipe.transform(value.speed, 'filesize');
|
||||
|
||||
return `Downloading ${progress || 0}% (${speed}/s)`;
|
||||
return `Downloading ${progress.toFixed(2)}% (${speed}/s)`;
|
||||
}
|
||||
|
||||
if (value.downloadQueued) {
|
||||
|
|
|
|||
|
|
@ -4,9 +4,17 @@ export class Torrent {
|
|||
public torrentId: string;
|
||||
public hash: string;
|
||||
public category: string;
|
||||
public downloadAction: number;
|
||||
public finishedAction: number;
|
||||
public downloadMinSize: number;
|
||||
public downloadManualFiles: string;
|
||||
|
||||
public added: Date;
|
||||
public filesSelected: Date;
|
||||
public completed: Date;
|
||||
public autoDelete: boolean;
|
||||
|
||||
public fileOrMagnet: string;
|
||||
public isFile: boolean;
|
||||
|
||||
public rdId: string;
|
||||
public rdName: string;
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@
|
|||
<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.
|
||||
Real-Debrid having to re-download the torrent.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -155,10 +155,10 @@
|
|||
<div class="control">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" [(ngModel)]="settingOnlyDownloadAvailableFiles" />
|
||||
Only download available files on RealDebrid
|
||||
Only download available files on Real-Debrid
|
||||
</label>
|
||||
<div class="help">
|
||||
When selected, it will only download files in the torrent that have been download by Real Debrid. You can use
|
||||
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>
|
||||
|
|
@ -189,7 +189,7 @@
|
|||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Test RealDebrid download speed</label>
|
||||
<label class="label">Test Real-Debrid download speed</label>
|
||||
<div class="control">
|
||||
<button
|
||||
class="button is-warning"
|
||||
|
|
@ -210,7 +210,7 @@
|
|||
</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.
|
||||
This will attempt to download a 10GB file from Real-Debrid. When 50MB has been downloaded the test will stop.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<img src="../../assets/logo.png" />
|
||||
<div class="box" *ngIf="step === 1">
|
||||
<div class="notification is-primary">
|
||||
Welcome to RealDebrid Client. Please create your account by entering a username and password.
|
||||
Welcome to Real-Debrid Client. Please create your account by entering a username and password.
|
||||
</div>
|
||||
<form (ngSubmit)="setup()">
|
||||
<div class="field">
|
||||
|
|
@ -44,14 +44,14 @@
|
|||
|
||||
<div class="box" *ngIf="step === 2">
|
||||
<div class="notification is-primary">
|
||||
To be able to use the RealDebrid Client you need a premium subscription to download torrents.
|
||||
To be able to use the Real-Debrid Client you need a premium subscription to download torrents.
|
||||
<br /><br />
|
||||
Not premium yet?
|
||||
<a href="https://real-debrid.com/?id=1348683" target="_blank" rel="noopener"
|
||||
>Use this link to sign up to RealDebrid.</a
|
||||
>Use this link to sign up to Real-Debrid.</a
|
||||
>
|
||||
<br /><br />
|
||||
To connect to RealDebrid please enter your Personal Prive API Token found here:
|
||||
To connect to Real-Debrid please enter your Personal Prive API Token found here:
|
||||
<a href="https://real-debrid.com/apitoken" target="_blank" rel="noopener"
|
||||
>https://real-debrid.com/apitoken</a
|
||||
>.
|
||||
|
|
@ -82,7 +82,7 @@
|
|||
|
||||
<div class="box" *ngIf="step === 3">
|
||||
<div class="notification is-primary">
|
||||
You are now connected to RealDebrid! To setup Radarr or Sonarr, please following the instructions here:
|
||||
You are now connected to Real-Debrid! To setup Radarr or Sonarr, please following the instructions here:
|
||||
<a href="https://github.com/rogerfar/rdt-client/" target="_blank" rel="noopener"
|
||||
>https://github.com/rogerfar/rdt-client/</a
|
||||
>.
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
<td colspan="4">
|
||||
<i class="fas fa-download" aria-hidden="true"></i>
|
||||
<span *ngIf="download.link">{{ download.link }}</span>
|
||||
<span *ngIf="!download.link">{{ download.path }}</span>
|
||||
</td>
|
||||
<td>
|
||||
{{ download.bytesTotal | filesize }}
|
||||
</td>
|
||||
<td>
|
||||
{{ download | downloadStatus }}
|
||||
</td>
|
||||
<td></td>
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
td {
|
||||
font-size: smaller;
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { Download } from '../models/download.model';
|
||||
|
||||
@Component({
|
||||
selector: '[app-torrent-download]',
|
||||
templateUrl: './torrent-download.component.html',
|
||||
styleUrls: ['./torrent-download.component.scss'],
|
||||
})
|
||||
export class TorrentDownloadComponent implements OnInit {
|
||||
@Input()
|
||||
public download: Download;
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnInit(): void {}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
<td colspan="4">
|
||||
<i class="fas fa-file" aria-hidden="true"></i>
|
||||
{{ file.path }}
|
||||
</td>
|
||||
<td>{{ file.bytes | filesize }}</td>
|
||||
<td colspan="10"></td>
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
td {
|
||||
font-size: smaller;
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { TorrentFile } from '../models/torrent.model';
|
||||
|
||||
@Component({
|
||||
selector: '[app-torrent-file]',
|
||||
templateUrl: './torrent-file.component.html',
|
||||
styleUrls: ['./torrent-file.component.scss'],
|
||||
})
|
||||
export class TorrentFileComponent implements OnInit {
|
||||
@Input()
|
||||
public file: TorrentFile;
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnInit(): void {}
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
<td>{{ torrent.rdName }}</td>
|
||||
<td>
|
||||
{{ torrent.files.length | number }}
|
||||
</td>
|
||||
<td>
|
||||
{{ torrent.downloads.length | number }}
|
||||
</td>
|
||||
<td class="auto">
|
||||
<i class="fas fa-trash" *ngIf="torrent.autoDelete" title="Auto delete"></i>
|
||||
</td>
|
||||
<td>
|
||||
{{ torrent.rdSize | filesize }}
|
||||
</td>
|
||||
<td>
|
||||
{{ torrent | status }}
|
||||
</td>
|
||||
<td>
|
||||
<span class="icon sync-icon" (click)="retryClick($event)" title="Retry" *ngIf="!loading">
|
||||
<i class="fas fa-sync"></i>
|
||||
</span>
|
||||
<span class="icon delete-icon" (click)="deleteClick($event)" title="Delete torrent" *ngIf="!loading">
|
||||
<i class="fas fa-times"></i>
|
||||
</span>
|
||||
<span class="icon loading-icon" *ngIf="loading">
|
||||
<i class="fas fa-spinner fa-pulse"></i>
|
||||
</span>
|
||||
</td>
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
.delete-icon {
|
||||
color: red;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.auto .fas {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { Torrent } from 'src/app/models/torrent.model';
|
||||
|
||||
@Component({
|
||||
selector: '[app-torrent-row]',
|
||||
templateUrl: './torrent-row.component.html',
|
||||
styleUrls: ['./torrent-row.component.scss'],
|
||||
})
|
||||
export class TorrentRowComponent implements OnInit {
|
||||
@Input()
|
||||
public torrent: Torrent;
|
||||
|
||||
@Output('delete')
|
||||
public delete = new EventEmitter();
|
||||
|
||||
@Output('retry')
|
||||
public retry = new EventEmitter();
|
||||
|
||||
public loading = false;
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnInit(): void {}
|
||||
|
||||
public deleteClick(event: Event): void {
|
||||
event.stopPropagation();
|
||||
this.delete.emit(this.torrent.torrentId);
|
||||
}
|
||||
|
||||
public retryClick(event: Event): void {
|
||||
event.stopPropagation();
|
||||
this.retry.emit(this.torrent.torrentId);
|
||||
}
|
||||
}
|
||||
|
|
@ -28,6 +28,11 @@ export class TorrentStatusPipe implements PipeTransform {
|
|||
const bytesDone = downloading.sum((m) => m.bytesDone);
|
||||
const bytesTotal = downloading.sum((m) => m.bytesTotal);
|
||||
let progress = (bytesDone / bytesTotal) * 100;
|
||||
|
||||
if (isNaN(progress)) {
|
||||
progress = 0;
|
||||
}
|
||||
|
||||
let allSpeeds = downloading.sum((m) => m.speed) / downloading.length;
|
||||
|
||||
let speed: string | string[] = '0';
|
||||
|
|
@ -44,6 +49,11 @@ export class TorrentStatusPipe implements PipeTransform {
|
|||
const bytesDone = unpacking.sum((m) => m.bytesDone);
|
||||
const bytesTotal = unpacking.sum((m) => m.bytesTotal);
|
||||
let progress = (bytesDone / bytesTotal) * 100;
|
||||
|
||||
if (isNaN(progress)) {
|
||||
progress = 0;
|
||||
}
|
||||
|
||||
let allSpeeds = unpacking.sum((m) => m.speed) / unpacking.length;
|
||||
|
||||
if (allSpeeds > 0) {
|
||||
|
|
|
|||
|
|
@ -7,126 +7,34 @@
|
|||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Category</th>
|
||||
<th>Files</th>
|
||||
<th>Downloads</th>
|
||||
<th>Auto</th>
|
||||
<th>Size</th>
|
||||
<th>Status</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<ng-container *ngFor="let torrent of torrents; trackBy: trackByMethod">
|
||||
<tr
|
||||
app-torrent-row
|
||||
[torrent]="torrent"
|
||||
(click)="selectTorrent(torrent)"
|
||||
(delete)="showDeleteModal($event)"
|
||||
(retry)="showRetryModal($event)"
|
||||
></tr>
|
||||
<ng-container *ngIf="showFiles[torrent.torrentId]">
|
||||
<tr class="separator">
|
||||
<td colspan="10">Downloads</td>
|
||||
</tr>
|
||||
<tr app-torrent-download [download]="download" *ngFor="let download of torrent.downloads"></tr>
|
||||
<tr class="separator">
|
||||
<td colspan="10">Files in torrent</td>
|
||||
</tr>
|
||||
<tr app-torrent-file [file]="file" *ngFor="let file of torrent.files"></tr>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<tr (click)="selectTorrent(torrent.torrentId)" *ngFor="let torrent of torrents; trackBy: trackByMethod">
|
||||
<td>
|
||||
{{ torrent.rdName }}
|
||||
</td>
|
||||
<td>
|
||||
{{ torrent.category }}
|
||||
</td>
|
||||
<td>
|
||||
{{ torrent.files.length | number }}
|
||||
</td>
|
||||
<td>
|
||||
{{ torrent.downloads.length | number }}
|
||||
</td>
|
||||
<td>
|
||||
{{ torrent.rdSize | filesize }}
|
||||
</td>
|
||||
<td>
|
||||
{{ torrent | status }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="modal" [class.is-active]="isDeleteModalActive">
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">Delete torrent</p>
|
||||
<button class="delete" aria-label="close" (click)="deleteCancel()"></button>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
<div class="field">
|
||||
<label class="label"></label>
|
||||
<div class="control">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" [(ngModel)]="deleteData" />
|
||||
Delete Torrent from local RealDebrid Client
|
||||
</label>
|
||||
<br />
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" [(ngModel)]="deleteRdTorrent" />
|
||||
Delete Torrent from RealDebrid
|
||||
</label>
|
||||
<br />
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" [(ngModel)]="deleteLocalFiles" />
|
||||
Delete local files
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="notification is-primary">
|
||||
Deleting a torrent from RealDebrid will automatically delete it here too.
|
||||
</div>
|
||||
<div class="notification is-danger is-light" *ngIf="deleteError?.length > 0">
|
||||
Error deleting torrent: {{ deleteError }}
|
||||
</div>
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
<button
|
||||
class="button is-success"
|
||||
(click)="deleteOk()"
|
||||
[disabled]="deleting"
|
||||
[ngClass]="{ 'is-loading': deleting }"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
<button class="button" (click)="deleteCancel()" [disabled]="deleting" [ngClass]="{ 'is-loading': deleting }">
|
||||
Cancel
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal" [class.is-active]="isRetryModalActive">
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">Retry torrent</p>
|
||||
<button class="delete" aria-label="close" (click)="retryCancel()"></button>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
<div class="field">
|
||||
<label class="label">Retry</label>
|
||||
<div class="control">
|
||||
<label class="radio">
|
||||
<input type="radio" name="retry" [value]="0" [(ngModel)]="retry" />
|
||||
Retry on Real-Debrid
|
||||
</label>
|
||||
<br />
|
||||
<label class="radio">
|
||||
<input type="radio" name="retry" [value]="1" [(ngModel)]="retry" />
|
||||
Retry downloading locally
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="notification is-danger is-light" *ngIf="retryError?.length > 0">
|
||||
Error retrying torrent: {{ retryError }}
|
||||
</div>
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
<button
|
||||
class="button is-success"
|
||||
(click)="retryOk()"
|
||||
[disabled]="retrying"
|
||||
[ngClass]="{ 'is-loading': retrying }"
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
<button class="button" (click)="retryCancel()" [disabled]="retrying" [ngClass]="{ 'is-loading': retrying }">
|
||||
Cancel
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,11 +2,4 @@ table {
|
|||
tr {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
tr.separator {
|
||||
td {
|
||||
font-size: smaller;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Torrent } from '../models/torrent.model';
|
||||
import { TorrentService } from '../torrent.service';
|
||||
|
||||
|
|
@ -10,23 +11,8 @@ import { TorrentService } from '../torrent.service';
|
|||
export class TorrentTableComponent implements OnInit, OnDestroy {
|
||||
public torrents: Torrent[] = [];
|
||||
public error: string;
|
||||
public showFiles: { [key: string]: boolean } = {};
|
||||
|
||||
public isDeleteModalActive: boolean;
|
||||
public deleteError: string;
|
||||
public deleting: boolean;
|
||||
public deleteTorrentId: string;
|
||||
public deleteData: boolean;
|
||||
public deleteRdTorrent: boolean;
|
||||
public deleteLocalFiles: boolean;
|
||||
|
||||
public isRetryModalActive: boolean;
|
||||
public retryError: string;
|
||||
public retrying: boolean;
|
||||
public retryTorrentId: string;
|
||||
public retry: number;
|
||||
|
||||
constructor(private torrentService: TorrentService) {}
|
||||
constructor(private router: Router, private torrentService: TorrentService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.torrentService.getList().subscribe(
|
||||
|
|
@ -49,67 +35,11 @@ export class TorrentTableComponent implements OnInit, OnDestroy {
|
|||
this.torrentService.disconnect();
|
||||
}
|
||||
|
||||
public selectTorrent(torrent: Torrent): void {
|
||||
this.showFiles[torrent.torrentId] = !this.showFiles[torrent.torrentId];
|
||||
public selectTorrent(torrentId: string): void {
|
||||
this.router.navigate([`/torrent/${torrentId}`]);
|
||||
}
|
||||
|
||||
public trackByMethod(index: number, el: Torrent): string {
|
||||
return el.torrentId;
|
||||
}
|
||||
|
||||
public showDeleteModal(torrentId: string): void {
|
||||
this.deleteData = false;
|
||||
this.deleteRdTorrent = false;
|
||||
this.deleteLocalFiles = false;
|
||||
|
||||
this.deleteTorrentId = torrentId;
|
||||
this.isDeleteModalActive = true;
|
||||
}
|
||||
|
||||
public deleteCancel(): void {
|
||||
this.isDeleteModalActive = false;
|
||||
}
|
||||
|
||||
public deleteOk(): void {
|
||||
this.deleting = true;
|
||||
|
||||
this.torrentService
|
||||
.delete(this.deleteTorrentId, this.deleteData, this.deleteRdTorrent, this.deleteLocalFiles)
|
||||
.subscribe(
|
||||
() => {
|
||||
this.isDeleteModalActive = false;
|
||||
this.deleting = false;
|
||||
},
|
||||
(err) => {
|
||||
this.deleteError = err.error;
|
||||
this.deleting = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public showRetryModal(torrentId: string): void {
|
||||
this.retry = 0;
|
||||
|
||||
this.retryTorrentId = torrentId;
|
||||
this.isRetryModalActive = true;
|
||||
}
|
||||
|
||||
public retryCancel(): void {
|
||||
this.isRetryModalActive = false;
|
||||
}
|
||||
|
||||
public retryOk(): void {
|
||||
this.retrying = true;
|
||||
|
||||
this.torrentService.retry(this.retryTorrentId, this.retry).subscribe(
|
||||
() => {
|
||||
this.isRetryModalActive = false;
|
||||
this.retrying = false;
|
||||
},
|
||||
(err) => {
|
||||
this.retryError = err.error;
|
||||
this.retrying = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,10 @@ export class TorrentService {
|
|||
private connection: signalR.HubConnection;
|
||||
|
||||
public connect(): void {
|
||||
if (this.connection != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.connection = new signalR.HubConnectionBuilder().withUrl('/hub').withAutomaticReconnect().build();
|
||||
this.connection.start().catch((err) => console.error(err));
|
||||
|
||||
|
|
@ -31,6 +35,10 @@ export class TorrentService {
|
|||
return this.http.get<Torrent[]>(`/Api/Torrents`);
|
||||
}
|
||||
|
||||
public get(torrentId: string): Observable<Torrent> {
|
||||
return this.http.get<Torrent>(`/Api/Torrents/Get/${torrentId}`);
|
||||
}
|
||||
|
||||
public uploadMagnet(
|
||||
magnetLink: string,
|
||||
category: string,
|
||||
|
|
@ -91,9 +99,11 @@ export class TorrentService {
|
|||
});
|
||||
}
|
||||
|
||||
public retry(torrentId: string, retry: number): Observable<void> {
|
||||
return this.http.post<void>(`/Api/Torrents/Retry/${torrentId}`, {
|
||||
retry,
|
||||
});
|
||||
public retry(torrentId: string): Observable<void> {
|
||||
return this.http.post<void>(`/Api/Torrents/Retry/${torrentId}`, {});
|
||||
}
|
||||
|
||||
public retryDownload(downloadId: string): Observable<void> {
|
||||
return this.http.post<void>(`/Api/Torrents/RetryDownload/${downloadId}`, {});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
439
client/src/app/torrent/torrent.component.html
Normal file
439
client/src/app/torrent/torrent.component.html
Normal file
|
|
@ -0,0 +1,439 @@
|
|||
<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>Torrent Files</a>
|
||||
</li>
|
||||
<li [ngClass]="{ 'is-active': activeTab === 2 }" (click)="activeTab = 2">
|
||||
<a>Downloads</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div *ngIf="torrent == null">
|
||||
<div class="fa-3x">
|
||||
<i class="fas fa-spinner fa-spin"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="torrent != null">
|
||||
<div fxLayout.lt-lg="column" fxLayout.gt-sm="row" fxLayoutGap="20px" *ngIf="activeTab === 0">
|
||||
<div fxFlex>
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<button class="button is-danger" (click)="showDeleteModal()">Delete Torrent</button>
|
||||
</div>
|
||||
<div class="control">
|
||||
<button class="button is-primary" (click)="showRetryModal()">Retry Torrent</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Status</label>
|
||||
{{ torrent | status }}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Hash</label>
|
||||
{{ torrent.hash }}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Category</label>
|
||||
{{ torrent.category || "(no category set)" }}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Download action</label>
|
||||
<ng-container [ngSwitch]="torrent.downloadAction">
|
||||
<ng-container *ngSwitchCase="0">Download all files above a certain size</ng-container>
|
||||
<ng-container *ngSwitchCase="1"
|
||||
>Download all available files on Real-Debrid above a certain size</ng-container
|
||||
>
|
||||
<ng-container *ngSwitchCase="2">Pick files I want to download</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Finished action</label>
|
||||
<ng-container [ngSwitch]="torrent.finishedAction">
|
||||
<ng-container *ngSwitchCase="0">Do nothing</ng-container>
|
||||
<ng-container *ngSwitchCase="1">Remove torrent from Real-Debrid and Real-Debrid Client</ng-container>
|
||||
<ng-container *ngSwitchCase="2">Remove torrent from Real-Debrid</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Minimum file size to download</label>
|
||||
{{ torrent.downloadMinSize }}MB
|
||||
</div>
|
||||
<div class="field" *ngIf="!torrent.isFile">
|
||||
<label class="label">Magnet</label>
|
||||
<span [cdkCopyToClipboard]="torrent.fileOrMagnet" (click)="copied = true" *ngIf="!copied"
|
||||
>Click to copy magnet link to clipboard</span
|
||||
>
|
||||
<span *ngIf="copied">Link copied to clipboard!</span>
|
||||
</div>
|
||||
<div class="field" *ngIf="torrent.isFile">
|
||||
<label class="label">Torrent file</label>
|
||||
<span (click)="download()">Click to download torrent file</span>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Added on</label>
|
||||
{{ torrent.added | date: "fullDate" }} {{ torrent.added | date: "mediumTime" }}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Files selected on</label>
|
||||
<ng-container *ngIf="torrent.filesSelected == null">(no files selected yet) </ng-container>
|
||||
<ng-container *ngIf="torrent.filesSelected != null">
|
||||
{{ torrent.filesSelected | date: "fullDate" }} {{ torrent.filesSelected | date: "mediumTime" }}
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Completed on</label>
|
||||
<ng-container *ngIf="torrent.completed == null">(not completed yet) </ng-container>
|
||||
<ng-container *ngIf="torrent.completed != null">
|
||||
{{ torrent.completed | date: "fullDate" }} {{ torrent.completed | date: "mediumTime" }}
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
<div fxFlex>
|
||||
<div class="field">
|
||||
<label class="label">Real-Debrid ID</label>
|
||||
{{ torrent.rdId }}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Real-Debrid Name</label>
|
||||
{{ torrent.rdName }}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Real-Debrid Size</label>
|
||||
{{ torrent.rdSize | filesize }}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Real-Debrid Host</label>
|
||||
{{ torrent.rdHost }}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Real-Debrid Split</label>
|
||||
{{ torrent.rdSplit }}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Real-Debrid Progress</label>
|
||||
{{ torrent.rdProgress || 0 }}%
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Real-Debrid Status</label>
|
||||
<ng-container [ngSwitch]="torrent.rdStatus">
|
||||
<ng-container *ngSwitchCase="0">Processing</ng-container>
|
||||
<ng-container *ngSwitchCase="1">Waiting For File Selection</ng-container>
|
||||
<ng-container *ngSwitchCase="2">Downloading</ng-container>
|
||||
<ng-container *ngSwitchCase="3">Finished</ng-container>
|
||||
<ng-container *ngSwitchCase="99">Error</ng-container>
|
||||
</ng-container>
|
||||
({{ torrent.rdStatusRaw }})
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Real-Debrid Added</label>
|
||||
{{ torrent.rdAdded | date: "fullDate" }} {{ torrent.rdAdded | date: "mediumTime" }}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Real-Debrid Ended</label>
|
||||
{{ torrent.rdEnded | date: "fullDate" }} {{ torrent.rdEnded | date: "mediumTime" }}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Real-Debrid Speed</label>
|
||||
{{ torrent.rdSpeed || 0 }}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Real-Debrid Seeders</label>
|
||||
{{ torrent.rdSeeders || "0" }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="activeTab === 1">
|
||||
<div class="field">
|
||||
<table class="table is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Path</th>
|
||||
<th>Size</th>
|
||||
<th>Selected</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let file of torrent.files">
|
||||
<td>
|
||||
{{ file.id }}
|
||||
</td>
|
||||
<td>
|
||||
{{ file.path }}
|
||||
</td>
|
||||
<td>
|
||||
{{ file.bytes | filesize }}
|
||||
</td>
|
||||
<td>
|
||||
<i class="fas fa-check" *ngIf="file.selected" style="color: green"></i>
|
||||
<i class="fas fa-times" *ngIf="!file.selected" style="color: red"></i>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="activeTab === 2">
|
||||
<div class="field">
|
||||
<table class="table is-fullwidth is-hoverable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 35px"></th>
|
||||
<th>Link</th>
|
||||
<th>Size</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<ng-container *ngFor="let download of torrent.downloads">
|
||||
<tr (click)="downloadExpanded[download.downloadId] = !downloadExpanded[download.downloadId]">
|
||||
<td style="width: 35px">
|
||||
<i class="fas fa-caret-right" *ngIf="!downloadExpanded[download.downloadId]"></i>
|
||||
<i class="fas fa-caret-down" *ngIf="downloadExpanded[download.downloadId]"></i>
|
||||
</td>
|
||||
<td>
|
||||
<ng-container *ngIf="download.link">
|
||||
{{ download.link | decodeURI }}
|
||||
</ng-container>
|
||||
</td>
|
||||
<td>
|
||||
{{ download.bytesTotal | filesize }}
|
||||
</td>
|
||||
<td>
|
||||
{{ download | downloadStatus }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr *ngIf="downloadExpanded[download.downloadId]" class="separator">
|
||||
<td style="width: 35px"></td>
|
||||
<td colspan="5">
|
||||
<div fxLayout.lt-lg="column" fxLayout.gt-sm="row" fxLayoutGap="20px">
|
||||
<div fxFlex>
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<button class="button is-primary" (click)="showDownloadRetryModal(download.downloadId)">
|
||||
Retry Download
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field" *ngIf="download.error">
|
||||
<label class="label">Error</label>
|
||||
{{ download.error }}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Real-Debrid Unrestricted Link</label>
|
||||
<a href="{{ download.link }}" target="_blank" *ngIf="download.link">
|
||||
{{ download.link | decodeURI }}</a
|
||||
>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Real-Debrid Link</label>
|
||||
{{ download.path }}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Download</label>
|
||||
{{ download.bytesDone | filesize }} / {{ download.bytesTotal | filesize }} ({{
|
||||
download.speed | filesize
|
||||
}}/s)
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Retry Count</label>
|
||||
{{ download.retryCount }}
|
||||
</div>
|
||||
</div>
|
||||
<div fxFlex>
|
||||
<div class="field">
|
||||
<label class="label">Added</label>
|
||||
<ng-container *ngIf="download.added">
|
||||
{{ download.added | date: "fullDate" }} {{ download.added | date: "mediumTime" }}
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!download.added">(not added yet) </ng-container>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Download Queued</label>
|
||||
<ng-container *ngIf="download.downloadQueued">
|
||||
{{ download.downloadQueued | date: "fullDate" }}
|
||||
{{ download.downloadQueued | date: "mediumTime" }}
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!download.downloadQueued">(not queued for downloading yet) </ng-container>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Download Started</label>
|
||||
<ng-container *ngIf="download.downloadStarted">
|
||||
{{ download.downloadStarted | date: "fullDate" }}
|
||||
{{ download.downloadStarted | date: "mediumTime" }}
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!download.downloadStarted">(not started downloading yet) </ng-container>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Download Finished</label>
|
||||
<ng-container *ngIf="download.downloadFinished">
|
||||
{{ download.downloadFinished | date: "fullDate" }}
|
||||
{{ download.downloadFinished | date: "mediumTime" }}
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!download.downloadFinished">(not finished yet) </ng-container>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Unpacking Queued</label>
|
||||
<ng-container *ngIf="download.unpackingQueued">
|
||||
{{ download.unpackingQueued | date: "fullDate" }}
|
||||
{{ download.unpackingQueued | date: "mediumTime" }}
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!download.unpackingQueued">(not queued for unpacking yet) </ng-container>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Unpacking Started</label>
|
||||
<ng-container *ngIf="download.unpackingStarted">
|
||||
{{ download.unpackingStarted | date: "fullDate" }}
|
||||
{{ download.unpackingStarted | date: "mediumTime" }}
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!download.unpackingStarted">(not started unpacking yet) </ng-container>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Unpacking Finished</label>
|
||||
<ng-container *ngIf="download.unpackingFinished">
|
||||
{{ download.unpackingFinished | date: "fullDate" }}
|
||||
{{ download.unpackingFinished | date: "mediumTime" }}
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!download.unpackingFinished">(not finished unpacking yet) </ng-container>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Completed</label>
|
||||
<ng-container *ngIf="download.completed">
|
||||
{{ download.completed | date: "fullDate" }}
|
||||
{{ download.completed | date: "mediumTime" }}
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!download.completed">(not completed yet) </ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal" [class.is-active]="isDeleteModalActive">
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">Delete torrent</p>
|
||||
<button class="delete" aria-label="close" (click)="deleteCancel()"></button>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
<p>Are you sure you want to delete this torrent?</p>
|
||||
<div class="field">
|
||||
<label class="label"></label>
|
||||
<div class="control">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" [(ngModel)]="deleteData" />
|
||||
Delete Torrent from local Real-Debrid Client
|
||||
</label>
|
||||
<br />
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" [(ngModel)]="deleteRdTorrent" />
|
||||
Delete Torrent from Real-Debrid
|
||||
</label>
|
||||
<br />
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" [(ngModel)]="deleteLocalFiles" />
|
||||
Delete local files
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="notification is-primary">
|
||||
Deleting a torrent from Real-Debrid will automatically delete it here too.
|
||||
</div>
|
||||
<div class="notification is-danger is-light" *ngIf="deleteError?.length > 0">
|
||||
Error deleting torrent: {{ deleteError }}
|
||||
</div>
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
<button
|
||||
class="button is-success"
|
||||
(click)="deleteOk()"
|
||||
[disabled]="deleting"
|
||||
[ngClass]="{ 'is-loading': deleting }"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
<button class="button" (click)="deleteCancel()" [disabled]="deleting" [ngClass]="{ 'is-loading': deleting }">
|
||||
Cancel
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal" [class.is-active]="isRetryModalActive">
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">Retry torrent</p>
|
||||
<button class="delete" aria-label="close" (click)="retryCancel()"></button>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
<p>Are you sure you want to retry this torrent?</p>
|
||||
<p>
|
||||
This action will delete all the torrent data + all local downloads. Then it will re-add the original magnet link
|
||||
or torrent file to Real-Debrid.
|
||||
</p>
|
||||
<div class="notification is-danger is-light" *ngIf="retryError?.length > 0">
|
||||
Error retrying torrent: {{ retryError }}
|
||||
</div>
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
<button
|
||||
class="button is-success"
|
||||
(click)="retryOk()"
|
||||
[disabled]="retrying"
|
||||
[ngClass]="{ 'is-loading': retrying }"
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
<button class="button" (click)="retryCancel()" [disabled]="retrying" [ngClass]="{ 'is-loading': retrying }">
|
||||
Cancel
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal" [class.is-active]="isDownloadRetryModalActive">
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">Retry download</p>
|
||||
<button class="delete" aria-label="close" (click)="downloadRetryCancel()"></button>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
<p>Are you sure you want to retry this download?</p>
|
||||
<p>This action will remove the local download and re-download the file from Real-Debrid.</p>
|
||||
<div class="notification is-danger is-light" *ngIf="downloadRetryError?.length > 0">
|
||||
Error retrying download: {{ downloadRetryError }}
|
||||
</div>
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
<button
|
||||
class="button is-success"
|
||||
(click)="downloadRetryOk()"
|
||||
[disabled]="downloadRetrying"
|
||||
[ngClass]="{ 'is-loading': downloadRetrying }"
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
<button
|
||||
class="button"
|
||||
(click)="downloadRetryCancel()"
|
||||
[disabled]="downloadRetrying"
|
||||
[ngClass]="{ 'is-loading': downloadRetrying }"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
13
client/src/app/torrent/torrent.component.scss
Normal file
13
client/src/app/torrent/torrent.component.scss
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
.label {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
table {
|
||||
tr {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.fa-download {
|
||||
margin-left :12px;
|
||||
}
|
||||
168
client/src/app/torrent/torrent.component.ts
Normal file
168
client/src/app/torrent/torrent.component.ts
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { Torrent } from '../models/torrent.model';
|
||||
import { TorrentService } from '../torrent.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-torrent',
|
||||
templateUrl: './torrent.component.html',
|
||||
styleUrls: ['./torrent.component.scss'],
|
||||
})
|
||||
export class TorrentComponent implements OnInit {
|
||||
public torrent: Torrent;
|
||||
|
||||
public activeTab: number = 0;
|
||||
|
||||
public copied: boolean = false;
|
||||
|
||||
public downloadExpanded: { [downloadId: string]: boolean } = {};
|
||||
|
||||
public isDeleteModalActive: boolean;
|
||||
public deleteError: string;
|
||||
public deleting: boolean;
|
||||
public deleteData: boolean;
|
||||
public deleteRdTorrent: boolean;
|
||||
public deleteLocalFiles: boolean;
|
||||
|
||||
public isRetryModalActive: boolean;
|
||||
public retryError: string;
|
||||
public retrying: boolean;
|
||||
|
||||
public isDownloadRetryModalActive: boolean;
|
||||
public downloadRetryError: string;
|
||||
public downloadRetrying: boolean;
|
||||
public downloadRetryId: string;
|
||||
|
||||
constructor(private activatedRoute: ActivatedRoute, private router: Router, private torrentService: TorrentService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.activatedRoute.params.subscribe((params) => {
|
||||
const torrentId = params['id'];
|
||||
|
||||
this.torrentService.get(torrentId).subscribe(
|
||||
(torrent) => {
|
||||
this.torrent = torrent;
|
||||
|
||||
this.torrentService.connect();
|
||||
|
||||
this.torrentService.update$.subscribe((result) => {
|
||||
this.update(result);
|
||||
});
|
||||
},
|
||||
() => {
|
||||
this.router.navigate(['/']);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public update(torrents: Torrent[]): void {
|
||||
const updatedTorrent = torrents.firstOrDefault((m) => m.torrentId === this.torrent.torrentId);
|
||||
|
||||
if (updatedTorrent == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.torrent = updatedTorrent;
|
||||
}
|
||||
|
||||
public download(): void {
|
||||
const byteArray = new Uint8Array(
|
||||
window
|
||||
.atob(this.torrent.fileOrMagnet)
|
||||
.split('')
|
||||
.map(function (c) {
|
||||
return c.charCodeAt(0);
|
||||
})
|
||||
);
|
||||
|
||||
var blob = new Blob([byteArray], { type: 'application/x-bittorrent' });
|
||||
saveAs(blob, `${this.torrent.rdName}.torrent`);
|
||||
}
|
||||
|
||||
public showDeleteModal(): void {
|
||||
this.deleteData = false;
|
||||
this.deleteRdTorrent = false;
|
||||
this.deleteLocalFiles = false;
|
||||
this.deleteError = null;
|
||||
|
||||
this.isDeleteModalActive = true;
|
||||
}
|
||||
|
||||
public deleteCancel(): void {
|
||||
this.isDeleteModalActive = false;
|
||||
}
|
||||
|
||||
public deleteOk(): void {
|
||||
this.deleting = true;
|
||||
|
||||
this.torrentService
|
||||
.delete(this.torrent.torrentId, this.deleteData, this.deleteRdTorrent, this.deleteLocalFiles)
|
||||
.subscribe(
|
||||
() => {
|
||||
this.isDeleteModalActive = false;
|
||||
this.deleting = false;
|
||||
|
||||
this.router.navigate(['/']);
|
||||
},
|
||||
(err) => {
|
||||
this.deleteError = err.error;
|
||||
this.deleting = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public showRetryModal(): void {
|
||||
this.retryError = null;
|
||||
|
||||
this.isRetryModalActive = true;
|
||||
}
|
||||
|
||||
public retryCancel(): void {
|
||||
this.isRetryModalActive = false;
|
||||
}
|
||||
|
||||
public retryOk(): void {
|
||||
this.retrying = true;
|
||||
|
||||
this.torrentService.retry(this.torrent.torrentId).subscribe(
|
||||
() => {
|
||||
this.isRetryModalActive = false;
|
||||
this.retrying = false;
|
||||
|
||||
this.router.navigate(['/']);
|
||||
},
|
||||
(err) => {
|
||||
this.retryError = err.error;
|
||||
this.retrying = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public showDownloadRetryModal(downloadId: string): void {
|
||||
this.downloadRetryId = downloadId;
|
||||
this.downloadRetryError = null;
|
||||
|
||||
this.isDownloadRetryModalActive = true;
|
||||
}
|
||||
|
||||
public downloadRetryCancel(): void {
|
||||
this.isDownloadRetryModalActive = false;
|
||||
}
|
||||
|
||||
public downloadRetryOk(): void {
|
||||
this.downloadRetrying = true;
|
||||
|
||||
this.torrentService.retryDownload(this.downloadRetryId).subscribe(
|
||||
() => {
|
||||
this.isDownloadRetryModalActive = false;
|
||||
this.downloadRetrying = false;
|
||||
},
|
||||
(err) => {
|
||||
this.downloadRetryError = err.error;
|
||||
this.downloadRetrying = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,29 +6,12 @@ using Download = RdtClient.Data.Models.Data.Download;
|
|||
|
||||
namespace RdtClient.Data.Data
|
||||
{
|
||||
public interface IDownloadData
|
||||
{
|
||||
Task<Download> GetById(Guid downloadId);
|
||||
Task<Download> Get(Guid torrentId, String path);
|
||||
Task<Download> Add(Guid torrentId, String path);
|
||||
Task UpdateUnrestrictedLink(Guid downloadId, String unrestrictedLink);
|
||||
Task UpdateDownloadStarted(Guid downloadId, DateTimeOffset? dateTime);
|
||||
Task UpdateDownloadFinished(Guid downloadId, DateTimeOffset? dateTime);
|
||||
Task UpdateUnpackingQueued(Guid downloadId, DateTimeOffset? dateTime);
|
||||
Task UpdateUnpackingStarted(Guid downloadId, DateTimeOffset? dateTime);
|
||||
Task UpdateUnpackingFinished(Guid downloadId, DateTimeOffset? dateTime);
|
||||
Task UpdateCompleted(Guid downloadId, DateTimeOffset? dateTime);
|
||||
Task UpdateError(Guid downloadId, String error);
|
||||
Task UpdateRetryCount(Guid downloadId, Int32 retryCount);
|
||||
Task DeleteForTorrent(Guid torrentId);
|
||||
}
|
||||
|
||||
public class DownloadData : IDownloadData
|
||||
public class DownloadData
|
||||
{
|
||||
private readonly DataContext _dataContext;
|
||||
private readonly ITorrentData _torrentData;
|
||||
private readonly TorrentData _torrentData;
|
||||
|
||||
public DownloadData(DataContext dataContext, ITorrentData torrentData)
|
||||
public DownloadData(DataContext dataContext, TorrentData torrentData)
|
||||
{
|
||||
_dataContext = dataContext;
|
||||
_torrentData = torrentData;
|
||||
|
|
@ -44,7 +27,9 @@ namespace RdtClient.Data.Data
|
|||
|
||||
public async Task<Download> Get(Guid torrentId, String path)
|
||||
{
|
||||
return await _dataContext.Downloads.FirstOrDefaultAsync(m => m.TorrentId == torrentId && m.Path == path);
|
||||
return await _dataContext.Downloads
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(m => m.TorrentId == torrentId && m.Path == path);
|
||||
}
|
||||
|
||||
public async Task<Download> Add(Guid torrentId, String path)
|
||||
|
|
@ -193,5 +178,27 @@ namespace RdtClient.Data.Data
|
|||
|
||||
await _torrentData.VoidCache();
|
||||
}
|
||||
|
||||
public async Task Reset(Guid downloadId)
|
||||
{
|
||||
var dbDownload = await _dataContext.Downloads
|
||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||
|
||||
dbDownload.RetryCount = 0;
|
||||
dbDownload.Link = null;
|
||||
dbDownload.Added = DateTimeOffset.UtcNow;
|
||||
dbDownload.DownloadQueued = DateTimeOffset.UtcNow;
|
||||
dbDownload.DownloadStarted = null;
|
||||
dbDownload.DownloadFinished = null;
|
||||
dbDownload.UnpackingQueued = null;
|
||||
dbDownload.UnpackingStarted = null;
|
||||
dbDownload.UnpackingFinished = null;
|
||||
dbDownload.Completed = null;
|
||||
dbDownload.Error = null;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await _torrentData.VoidCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,14 +8,7 @@ using RdtClient.Data.Models.Data;
|
|||
|
||||
namespace RdtClient.Data.Data
|
||||
{
|
||||
public interface ISettingData
|
||||
{
|
||||
Task<IList<Setting>> GetAll();
|
||||
Task Update(IList<Setting> settings);
|
||||
Task UpdateString(String key, String value);
|
||||
}
|
||||
|
||||
public class SettingData : ISettingData
|
||||
public class SettingData
|
||||
{
|
||||
private static IList<Setting> _settingCache;
|
||||
private static readonly SemaphoreSlim _settingCacheLock = new(1);
|
||||
|
|
|
|||
|
|
@ -9,31 +9,7 @@ using RdtClient.Data.Models.Data;
|
|||
|
||||
namespace RdtClient.Data.Data
|
||||
{
|
||||
public interface ITorrentData
|
||||
{
|
||||
Task<IList<Torrent>> Get();
|
||||
Task<Torrent> GetById(Guid torrentId);
|
||||
Task<Torrent> GetByHash(String hash);
|
||||
|
||||
Task<Torrent> Add(String realDebridId,
|
||||
String hash,
|
||||
String category,
|
||||
TorrentDownloadAction downloadAction,
|
||||
TorrentFinishedAction finishedAction,
|
||||
Int32 downloadMinSize,
|
||||
String downloadManualFiles,
|
||||
String fileOrMagnetContents,
|
||||
Boolean isFile);
|
||||
|
||||
Task UpdateRdData(Torrent torrent);
|
||||
Task UpdateCategory(Guid torrentId, String category);
|
||||
Task UpdateComplete(Guid torrentId, DateTimeOffset? datetime);
|
||||
Task UpdateFilesSelected(Guid torrentId, DateTimeOffset datetime);
|
||||
Task Delete(Guid torrentId);
|
||||
Task VoidCache();
|
||||
}
|
||||
|
||||
public class TorrentData : ITorrentData
|
||||
public class TorrentData
|
||||
{
|
||||
private static IList<Torrent> _torrentCache;
|
||||
private static readonly SemaphoreSlim _torrentCacheLock = new(1);
|
||||
|
|
|
|||
|
|
@ -4,12 +4,7 @@ using Microsoft.EntityFrameworkCore;
|
|||
|
||||
namespace RdtClient.Data.Data
|
||||
{
|
||||
public interface IUserData
|
||||
{
|
||||
Task<IdentityUser> GetUser();
|
||||
}
|
||||
|
||||
public class UserData : IUserData
|
||||
public class UserData
|
||||
{
|
||||
private readonly DataContext _dataContext;
|
||||
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ namespace RdtClient.Data
|
|||
{
|
||||
public static void Config(IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IDownloadData, DownloadData>();
|
||||
services.AddScoped<ISettingData, SettingData>();
|
||||
services.AddScoped<ITorrentData, TorrentData>();
|
||||
services.AddScoped<IUserData, UserData>();
|
||||
services.AddScoped<DownloadData>();
|
||||
services.AddScoped<SettingData>();
|
||||
services.AddScoped<TorrentData>();
|
||||
services.AddScoped<UserData>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,13 +7,13 @@ namespace RdtClient.Service
|
|||
{
|
||||
public static void Config(IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IAuthentication, Authentication>();
|
||||
services.AddScoped<IDownloads, Downloads>();
|
||||
services.AddScoped<IQBittorrent, QBittorrent>();
|
||||
services.AddScoped<IRemoteService, RemoteService>();
|
||||
services.AddScoped<ISettings, Settings>();
|
||||
services.AddScoped<ITorrents, Torrents>();
|
||||
services.AddScoped<ITorrentRunner, TorrentRunner>();
|
||||
services.AddScoped<Authentication>();
|
||||
services.AddScoped<Downloads>();
|
||||
services.AddScoped<QBittorrent>();
|
||||
services.AddScoped<RemoteService>();
|
||||
services.AddScoped<Settings>();
|
||||
services.AddScoped<Torrents>();
|
||||
services.AddScoped<TorrentRunner>();
|
||||
}
|
||||
}
|
||||
}
|
||||
37
server/RdtClient.Service/Helpers/DownloadHelper.cs
Normal file
37
server/RdtClient.Service/Helpers/DownloadHelper.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using RdtClient.Data.Models.Data;
|
||||
|
||||
namespace RdtClient.Service.Helpers
|
||||
{
|
||||
public static class DownloadHelper
|
||||
{
|
||||
public static String GetDownloadPath(String downloadPath, Torrent torrent, Download download)
|
||||
{
|
||||
var fileUrl = download.Link;
|
||||
|
||||
if (String.IsNullOrWhiteSpace(fileUrl))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var uri = new Uri(fileUrl);
|
||||
var torrentPath = Path.Combine(downloadPath, torrent.RdName);
|
||||
|
||||
if (!Directory.Exists(torrentPath))
|
||||
{
|
||||
Directory.CreateDirectory(torrentPath);
|
||||
}
|
||||
|
||||
var fileName = uri.Segments.Last();
|
||||
|
||||
fileName = HttpUtility.UrlDecode(fileName);
|
||||
|
||||
var filePath = Path.Combine(torrentPath, fileName);
|
||||
|
||||
return filePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,21 +5,13 @@ using RdtClient.Data.Data;
|
|||
|
||||
namespace RdtClient.Service.Services
|
||||
{
|
||||
public interface IAuthentication
|
||||
{
|
||||
Task<IdentityResult> Register(String userName, String password);
|
||||
Task<SignInResult> Login(String userName, String password);
|
||||
Task<IdentityUser> GetUser();
|
||||
Task Logout();
|
||||
}
|
||||
|
||||
public class Authentication : IAuthentication
|
||||
public class Authentication
|
||||
{
|
||||
private readonly SignInManager<IdentityUser> _signInManager;
|
||||
private readonly UserManager<IdentityUser> _userManager;
|
||||
private readonly IUserData _userData;
|
||||
private readonly UserData _userData;
|
||||
|
||||
public Authentication(SignInManager<IdentityUser> signInManager, UserManager<IdentityUser> userManager, IUserData userData)
|
||||
public Authentication(SignInManager<IdentityUser> signInManager, UserManager<IdentityUser> userManager, UserData userData)
|
||||
{
|
||||
_signInManager = signInManager;
|
||||
_userManager = userManager;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Downloader;
|
||||
using RdtClient.Data.Models.Data;
|
||||
using RdtClient.Service.Helpers;
|
||||
|
|
@ -44,36 +42,24 @@ namespace RdtClient.Service.Services
|
|||
|
||||
try
|
||||
{
|
||||
var downloadClientSetting = settings.GetString("DownloadClient");
|
||||
var filePath = DownloadHelper.GetDownloadPath(_destinationPath, _torrent, _download);
|
||||
|
||||
if (filePath == null)
|
||||
{
|
||||
throw new Exception("Invalid download path");
|
||||
}
|
||||
|
||||
var fileUrl = _download.Link;
|
||||
|
||||
if (String.IsNullOrWhiteSpace(fileUrl))
|
||||
{
|
||||
throw new Exception("File URL is empty");
|
||||
}
|
||||
|
||||
var uri = new Uri(fileUrl);
|
||||
var torrentPath = Path.Combine(_destinationPath, _torrent.RdName);
|
||||
|
||||
if (!Directory.Exists(torrentPath))
|
||||
{
|
||||
Directory.CreateDirectory(torrentPath);
|
||||
}
|
||||
|
||||
var fileName = uri.Segments.Last();
|
||||
|
||||
fileName = HttpUtility.UrlDecode(fileName);
|
||||
|
||||
var filePath = Path.Combine(torrentPath, fileName);
|
||||
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
File.Delete(filePath);
|
||||
}
|
||||
|
||||
var uri = new Uri(_download.Link);
|
||||
|
||||
await Task.Factory.StartNew(async delegate
|
||||
{
|
||||
var downloadClientSetting = settings.GetString("DownloadClient");
|
||||
|
||||
switch (downloadClientSetting)
|
||||
{
|
||||
case "Simple":
|
||||
|
|
|
|||
|
|
@ -5,28 +5,11 @@ using Download = RdtClient.Data.Models.Data.Download;
|
|||
|
||||
namespace RdtClient.Service.Services
|
||||
{
|
||||
public interface IDownloads
|
||||
public class Downloads
|
||||
{
|
||||
Task<Download> GetById(Guid downloadId);
|
||||
Task<Download> Get(Guid torrentId, String path);
|
||||
Task<Download> Add(Guid torrentId, String path);
|
||||
Task UpdateUnrestrictedLink(Guid downloadId, String unrestrictedLink);
|
||||
Task UpdateDownloadStarted(Guid downloadId, DateTimeOffset? dateTime);
|
||||
Task UpdateDownloadFinished(Guid downloadId, DateTimeOffset? dateTime);
|
||||
Task UpdateUnpackingQueued(Guid downloadId, DateTimeOffset? dateTime);
|
||||
Task UpdateUnpackingStarted(Guid downloadId, DateTimeOffset? dateTime);
|
||||
Task UpdateUnpackingFinished(Guid downloadId, DateTimeOffset? dateTime);
|
||||
Task UpdateCompleted(Guid downloadId, DateTimeOffset? dateTime);
|
||||
Task UpdateError(Guid downloadId, String error);
|
||||
Task UpdateRetryCount(Guid downloadId, Int32 retryCount);
|
||||
Task DeleteForTorrent(Guid torrentId);
|
||||
}
|
||||
private readonly DownloadData _downloadData;
|
||||
|
||||
public class Downloads : IDownloads
|
||||
{
|
||||
private readonly IDownloadData _downloadData;
|
||||
|
||||
public Downloads(IDownloadData downloadData)
|
||||
public Downloads(DownloadData downloadData)
|
||||
{
|
||||
_downloadData = downloadData;
|
||||
}
|
||||
|
|
@ -95,5 +78,10 @@ namespace RdtClient.Service.Services
|
|||
{
|
||||
await _downloadData.DeleteForTorrent(torrentId);
|
||||
}
|
||||
|
||||
public async Task Reset(Guid downloadId)
|
||||
{
|
||||
await _downloadData.Reset(downloadId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,31 +9,13 @@ using RdtClient.Service.Models.QBittorrent.QuickType;
|
|||
|
||||
namespace RdtClient.Service.Services
|
||||
{
|
||||
public interface IQBittorrent
|
||||
public class QBittorrent
|
||||
{
|
||||
Task<Boolean> AuthLogin(String userName, String password);
|
||||
Task AuthLogout();
|
||||
Task<AppPreferences> AppPreferences();
|
||||
Task<String> AppDefaultSavePath();
|
||||
Task<IList<TorrentInfo>> TorrentInfo();
|
||||
Task<IList<TorrentFileItem>> TorrentFileContents(String hash);
|
||||
Task<TorrentProperties> TorrentProperties(String hash);
|
||||
Task TorrentsDelete(String hash, Boolean deleteFiles);
|
||||
Task TorrentsAddMagnet(String magnetLink, String category);
|
||||
Task TorrentsAddFile(Byte[] fileBytes, String category);
|
||||
Task TorrentsSetCategory(String hash, String category);
|
||||
Task<IDictionary<String, TorrentCategory>> TorrentsCategories();
|
||||
Task CategoryCreate(String category);
|
||||
Task CategoryRemove(String category);
|
||||
}
|
||||
private readonly Authentication _authentication;
|
||||
private readonly Settings _settings;
|
||||
private readonly Torrents _torrents;
|
||||
|
||||
public class QBittorrent : IQBittorrent
|
||||
{
|
||||
private readonly IAuthentication _authentication;
|
||||
private readonly ISettings _settings;
|
||||
private readonly ITorrents _torrents;
|
||||
|
||||
public QBittorrent(ISettings settings, IAuthentication authentication, ITorrents torrents)
|
||||
public QBittorrent(Settings settings, Authentication authentication, Torrents torrents)
|
||||
{
|
||||
_settings = settings;
|
||||
_authentication = authentication;
|
||||
|
|
|
|||
|
|
@ -5,17 +5,12 @@ using Microsoft.AspNetCore.SignalR;
|
|||
|
||||
namespace RdtClient.Service.Services
|
||||
{
|
||||
public interface IRemoteService
|
||||
{
|
||||
Task Update();
|
||||
}
|
||||
|
||||
public class RemoteService : IRemoteService
|
||||
public class RemoteService
|
||||
{
|
||||
private readonly IHubContext<RdtHub> _hub;
|
||||
private readonly ITorrents _torrents;
|
||||
private readonly Torrents _torrents;
|
||||
|
||||
public RemoteService(IHubContext<RdtHub> hub, ITorrents torrents)
|
||||
public RemoteService(IHubContext<RdtHub> hub, Torrents torrents)
|
||||
{
|
||||
_hub = hub;
|
||||
_torrents = torrents;
|
||||
|
|
|
|||
|
|
@ -10,24 +10,11 @@ using RdtClient.Data.Models.Data;
|
|||
|
||||
namespace RdtClient.Service.Services
|
||||
{
|
||||
public interface ISettings
|
||||
public class Settings
|
||||
{
|
||||
Task<IList<Setting>> GetAll();
|
||||
Task Update(IList<Setting> settings);
|
||||
Task UpdateString(String key, String value);
|
||||
Task<String> GetString(String key);
|
||||
Task<Int32> GetNumber(String key);
|
||||
Task TestPath(String path);
|
||||
Task<Double> TestDownloadSpeed(CancellationToken cancellationToken);
|
||||
Task<Double> TestWriteSpeed();
|
||||
void Clean();
|
||||
}
|
||||
private readonly SettingData _settingData;
|
||||
|
||||
public class Settings : ISettings
|
||||
{
|
||||
private readonly ISettingData _settingData;
|
||||
|
||||
public Settings(ISettingData settingData)
|
||||
public Settings(SettingData settingData)
|
||||
{
|
||||
_settingData = settingData;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ namespace RdtClient.Service.Services
|
|||
await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken);
|
||||
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
var torrentRunner = scope.ServiceProvider.GetRequiredService<ITorrentRunner>();
|
||||
var torrentRunner = scope.ServiceProvider.GetRequiredService<TorrentRunner>();
|
||||
|
||||
_logger.LogInformation("TaskRunner started.");
|
||||
|
||||
|
|
@ -33,12 +33,18 @@ namespace RdtClient.Service.Services
|
|||
{
|
||||
try
|
||||
{
|
||||
await Torrents.TorrentResetLock.WaitAsync(stoppingToken);
|
||||
|
||||
await torrentRunner.Tick();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Unexpected error occurred in TorrentDownloadManager.Tick");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Torrents.TorrentResetLock.Release();
|
||||
}
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(2), stoppingToken);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,13 +11,7 @@ using Serilog;
|
|||
|
||||
namespace RdtClient.Service.Services
|
||||
{
|
||||
public interface ITorrentRunner
|
||||
{
|
||||
Task Initialize();
|
||||
Task Tick();
|
||||
}
|
||||
|
||||
public class TorrentRunner : ITorrentRunner
|
||||
public class TorrentRunner
|
||||
{
|
||||
private const Int32 RetryCount = 3;
|
||||
|
||||
|
|
@ -25,13 +19,13 @@ namespace RdtClient.Service.Services
|
|||
|
||||
public static readonly ConcurrentDictionary<Guid, DownloadClient> ActiveDownloadClients = new();
|
||||
public static readonly ConcurrentDictionary<Guid, UnpackClient> ActiveUnpackClients = new();
|
||||
private readonly IDownloads _downloads;
|
||||
private readonly IRemoteService _remoteService;
|
||||
private readonly Downloads _downloads;
|
||||
private readonly RemoteService _remoteService;
|
||||
|
||||
private readonly ISettings _settings;
|
||||
private readonly ITorrents _torrents;
|
||||
private readonly Settings _settings;
|
||||
private readonly Torrents _torrents;
|
||||
|
||||
public TorrentRunner(ISettings settings, ITorrents torrents, IDownloads downloads, IRemoteService remoteService)
|
||||
public TorrentRunner(Settings settings, Torrents torrents, Downloads downloads, RemoteService remoteService)
|
||||
{
|
||||
_settings = settings;
|
||||
_torrents = torrents;
|
||||
|
|
@ -174,10 +168,10 @@ namespace RdtClient.Service.Services
|
|||
|
||||
Log.Debug($"Found {torrents.Count} torrents");
|
||||
|
||||
// Only poll RealDebrid every second when a hub is connected, otherwise every 30 seconds
|
||||
// Only poll Real-Debrid every second when a hub is connected, otherwise every 30 seconds
|
||||
if (_nextUpdate < DateTime.UtcNow && torrents.Count > 0)
|
||||
{
|
||||
Log.Debug($"Updating torrent info from RealDebrid");
|
||||
Log.Debug($"Updating torrent info from Real-Debrid");
|
||||
|
||||
var updateTime = 30;
|
||||
|
||||
|
|
@ -193,7 +187,7 @@ namespace RdtClient.Service.Services
|
|||
// Re-get torrents to account for updated info
|
||||
torrents = await _torrents.Get();
|
||||
|
||||
Log.Debug($"Finished updating torrent info from RealDebrid, next update in {updateTime} seconds");
|
||||
Log.Debug($"Finished updating torrent info from Real-Debrid, next update in {updateTime} seconds");
|
||||
}
|
||||
|
||||
torrents = torrents.Where(m => m.Completed == null).ToList();
|
||||
|
|
@ -279,6 +273,13 @@ namespace RdtClient.Service.Services
|
|||
|
||||
var torrentDownload = torrents.First(m => m.TorrentId == download.TorrentId);
|
||||
|
||||
if (download.Link == null)
|
||||
{
|
||||
await _downloads.UpdateError(download.DownloadId, "Download Link cannot be null");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the unpacking process is even needed
|
||||
var uri = new Uri(download.Link);
|
||||
var fileName = uri.Segments.Last();
|
||||
|
|
@ -346,7 +347,7 @@ namespace RdtClient.Service.Services
|
|||
|
||||
foreach (var torrent in torrents)
|
||||
{
|
||||
// If torrent is erroring out on the RealDebrid side, skip processing this torrent.
|
||||
// If torrent is erroring out on the Real-Debrid side, skip processing this torrent.
|
||||
if (torrent.RdStatus == RealDebridStatus.Error)
|
||||
{
|
||||
Log.Debug($"Torrent {torrent.RdId} has an error: {torrent.RdStatusRaw}, not processing further");
|
||||
|
|
@ -354,14 +355,15 @@ namespace RdtClient.Service.Services
|
|||
continue;
|
||||
}
|
||||
|
||||
// The files are selected but there are no downloads yet, check if Real Debrid has generated links yet.
|
||||
// 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.
|
||||
// Real-Debrid is waiting for file selection, select which files to download.
|
||||
if ((torrent.RdStatus == RealDebridStatus.WaitingForFileSelection || torrent.RdStatus == RealDebridStatus.Finished) &&
|
||||
torrent.FilesSelected == null &&
|
||||
torrent.Downloads.Count == 0 &&
|
||||
torrent.FilesSelected == null)
|
||||
{
|
||||
|
|
@ -420,7 +422,7 @@ namespace RdtClient.Service.Services
|
|||
await _torrents.UpdateFilesSelected(torrent.TorrentId, DateTime.UtcNow);
|
||||
}
|
||||
|
||||
// RealDebrid finished downloading the torrent, process the file to host.
|
||||
// Real-Debrid finished downloading the torrent, process the file to host.
|
||||
if (torrent.RdStatus == RealDebridStatus.Finished)
|
||||
{
|
||||
// If the torrent has any files that need starting to be downloaded, download them.
|
||||
|
|
@ -483,11 +485,11 @@ namespace RdtClient.Service.Services
|
|||
switch (torrent.FinishedAction)
|
||||
{
|
||||
case TorrentFinishedAction.RemoveAllTorrents:
|
||||
Log.Debug($"Torrent {torrent.RdId} removing torrents from Real Debrid and Real Debrid Client, no files");
|
||||
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");
|
||||
Log.Debug($"Torrent {torrent.RdId} removing torrents from Real-Debrid, no files");
|
||||
await _torrents.Delete(torrent.TorrentId, false, true, false);
|
||||
break;
|
||||
case TorrentFinishedAction.None:
|
||||
|
|
|
|||
|
|
@ -9,57 +9,25 @@ using Newtonsoft.Json;
|
|||
using RDNET;
|
||||
using RdtClient.Data.Data;
|
||||
using RdtClient.Data.Enums;
|
||||
using RdtClient.Service.Helpers;
|
||||
using RdtClient.Service.Models;
|
||||
using Torrent = RdtClient.Data.Models.Data.Torrent;
|
||||
|
||||
namespace RdtClient.Service.Services
|
||||
{
|
||||
public interface ITorrents
|
||||
public class Torrents
|
||||
{
|
||||
Task<IList<Torrent>> Get();
|
||||
Task<Torrent> GetByHash(String hash);
|
||||
Task UpdateCategory(String hash, String category);
|
||||
public static readonly SemaphoreSlim TorrentResetLock = new(1, 1);
|
||||
|
||||
Task UploadMagnet(String magnetLink,
|
||||
String category,
|
||||
TorrentDownloadAction downloadAction,
|
||||
TorrentFinishedAction finishedAction,
|
||||
Int32 downloadMinSize,
|
||||
String downloadManualFiles);
|
||||
private static readonly SemaphoreSlim RealDebridUpdateLock = new(1, 1);
|
||||
|
||||
Task UploadFile(Byte[] bytes,
|
||||
String category,
|
||||
TorrentDownloadAction downloadAction,
|
||||
TorrentFinishedAction finishedAction,
|
||||
Int32 downloadMinSize,
|
||||
String downloadManualFiles);
|
||||
|
||||
Task<List<TorrentInstantAvailabilityFile>> GetAvailableFiles(String hash);
|
||||
Task SelectFiles(Guid torrentId, IList<String> fileIds);
|
||||
Task CheckForLinks(Guid torrentId);
|
||||
Task Delete(Guid torrentId, Boolean deleteData, Boolean deleteRdTorrent, Boolean deleteLocalFiles);
|
||||
Task<String> UnrestrictLink(Guid downloadId);
|
||||
Task Download(Guid downloadId);
|
||||
Task Unpack(Guid downloadId);
|
||||
void Reset();
|
||||
Task<Profile> GetProfile();
|
||||
Task UpdateComplete(Guid torrentId, DateTimeOffset datetime);
|
||||
Task UpdateFilesSelected(Guid torrentId, DateTimeOffset datetime);
|
||||
Task Update();
|
||||
Task Retry(Guid id, Int32 retry);
|
||||
}
|
||||
|
||||
public class Torrents : ITorrents
|
||||
{
|
||||
private static RdNetClient _rdtNetClient;
|
||||
|
||||
private static readonly SemaphoreSlim SemaphoreSlim = new(1, 1);
|
||||
private readonly Downloads _downloads;
|
||||
private readonly Settings _settings;
|
||||
private readonly TorrentData _torrentData;
|
||||
|
||||
private readonly IDownloads _downloads;
|
||||
private readonly ISettings _settings;
|
||||
private readonly ITorrentData _torrentData;
|
||||
|
||||
public Torrents(ITorrentData torrentData, ISettings settings, IDownloads downloads)
|
||||
public Torrents(TorrentData torrentData, Settings settings, Downloads downloads)
|
||||
{
|
||||
_torrentData = torrentData;
|
||||
_settings = settings;
|
||||
|
|
@ -271,6 +239,47 @@ namespace RdtClient.Service.Services
|
|||
}
|
||||
}
|
||||
|
||||
private async Task DeleteDownload(Guid torrentId, Guid downloadId)
|
||||
{
|
||||
var torrent = await GetById(torrentId);
|
||||
var download = await _downloads.GetById(downloadId);
|
||||
|
||||
var downloadPath = await DownloadPath(torrent);
|
||||
|
||||
var filePath = DownloadHelper.GetDownloadPath(downloadPath, torrent, download);
|
||||
|
||||
if (filePath == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
var retry = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(filePath);
|
||||
|
||||
break;
|
||||
}
|
||||
catch
|
||||
{
|
||||
retry++;
|
||||
|
||||
if (retry >= 3)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<String> UnrestrictLink(Guid downloadId)
|
||||
{
|
||||
var download = await _downloads.GetById(downloadId);
|
||||
|
|
@ -322,12 +331,7 @@ namespace RdtClient.Service.Services
|
|||
|
||||
public async Task Update()
|
||||
{
|
||||
var w = await SemaphoreSlim.WaitAsync(1);
|
||||
|
||||
if (!w)
|
||||
{
|
||||
return;
|
||||
}
|
||||
await RealDebridUpdateLock.WaitAsync();
|
||||
|
||||
var torrents = await Get();
|
||||
|
||||
|
|
@ -360,23 +364,42 @@ namespace RdtClient.Service.Services
|
|||
}
|
||||
finally
|
||||
{
|
||||
SemaphoreSlim.Release();
|
||||
RealDebridUpdateLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Retry(Guid id, Int32 retry)
|
||||
public async Task RetryTorrent(Guid torrentId)
|
||||
{
|
||||
var torrent = await _torrentData.GetById(id);
|
||||
var torrent = await _torrentData.GetById(torrentId);
|
||||
|
||||
if (retry == 0)
|
||||
foreach (var download in torrent.Downloads)
|
||||
{
|
||||
await Delete(id, true, true, true);
|
||||
|
||||
if (String.IsNullOrWhiteSpace(torrent.FileOrMagnet))
|
||||
while (TorrentRunner.ActiveDownloadClients.TryGetValue(download.DownloadId, out var downloadClient))
|
||||
{
|
||||
throw new Exception($"Cannot re-add this torrent, original magnet or file not found");
|
||||
downloadClient.Cancel();
|
||||
|
||||
await Task.Delay(100);
|
||||
}
|
||||
|
||||
while (TorrentRunner.ActiveUnpackClients.TryGetValue(download.DownloadId, out var unpackClient))
|
||||
{
|
||||
unpackClient.Cancel();
|
||||
|
||||
await Task.Delay(100);
|
||||
}
|
||||
}
|
||||
|
||||
await Delete(torrentId, true, true, true);
|
||||
|
||||
if (String.IsNullOrWhiteSpace(torrent.FileOrMagnet))
|
||||
{
|
||||
throw new Exception($"Cannot re-add this torrent, original magnet or file not found");
|
||||
}
|
||||
|
||||
await TorrentResetLock.WaitAsync();
|
||||
|
||||
try
|
||||
{
|
||||
if (torrent.IsFile)
|
||||
{
|
||||
var bytes = Convert.FromBase64String(torrent.FileOrMagnet);
|
||||
|
|
@ -393,19 +416,52 @@ namespace RdtClient.Service.Services
|
|||
torrent.DownloadManualFiles);
|
||||
}
|
||||
}
|
||||
else if (retry == 1)
|
||||
finally
|
||||
{
|
||||
await Delete(id, false, false, true);
|
||||
|
||||
await _torrentData.UpdateComplete(id, null);
|
||||
await _downloads.DeleteForTorrent(id);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Invalid retry option {retry}");
|
||||
TorrentResetLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RetryDownload(Guid downloadId)
|
||||
{
|
||||
var download = await _downloads.GetById(downloadId);
|
||||
|
||||
if (download == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
while (TorrentRunner.ActiveDownloadClients.TryGetValue(download.DownloadId, out var downloadClient))
|
||||
{
|
||||
downloadClient.Cancel();
|
||||
|
||||
await Task.Delay(100);
|
||||
}
|
||||
|
||||
while (TorrentRunner.ActiveUnpackClients.TryGetValue(download.DownloadId, out var unpackClient))
|
||||
{
|
||||
unpackClient.Cancel();
|
||||
|
||||
await Task.Delay(100);
|
||||
}
|
||||
|
||||
await TorrentResetLock.WaitAsync();
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
await DeleteDownload(download.TorrentId, download.DownloadId);
|
||||
|
||||
await _torrentData.UpdateComplete(download.TorrentId, null);
|
||||
|
||||
await _downloads.Reset(downloadId);
|
||||
}
|
||||
finally
|
||||
{
|
||||
TorrentResetLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateComplete(Guid torrentId, DateTimeOffset datetime)
|
||||
{
|
||||
await _torrentData.UpdateComplete(torrentId, datetime);
|
||||
|
|
@ -425,7 +481,7 @@ namespace RdtClient.Service.Services
|
|||
|
||||
if (String.IsNullOrWhiteSpace(apiKey))
|
||||
{
|
||||
throw new Exception("RealDebrid API Key not set in the settings");
|
||||
throw new Exception("Real-Debrid API Key not set in the settings");
|
||||
}
|
||||
|
||||
_rdtNetClient = new RdNetClient("X245A4XAIBGVM", null, null, null, apiKey);
|
||||
|
|
@ -441,6 +497,22 @@ namespace RdtClient.Service.Services
|
|||
if (torrent != null)
|
||||
{
|
||||
await Update(torrent);
|
||||
|
||||
foreach (var download in torrent.Downloads)
|
||||
{
|
||||
if (TorrentRunner.ActiveDownloadClients.TryGetValue(download.DownloadId, out var downloadClient))
|
||||
{
|
||||
download.Speed = downloadClient.Speed;
|
||||
download.BytesTotal = downloadClient.BytesTotal;
|
||||
download.BytesDone = downloadClient.BytesDone;
|
||||
}
|
||||
|
||||
if (TorrentRunner.ActiveUnpackClients.TryGetValue(download.DownloadId, out var unpackClient))
|
||||
{
|
||||
download.BytesTotal = unpackClient.BytesTotal;
|
||||
download.BytesDone = unpackClient.BytesDone;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return torrent;
|
||||
|
|
@ -456,13 +528,8 @@ namespace RdtClient.Service.Services
|
|||
String fileOrMagnetContents,
|
||||
Boolean isFile)
|
||||
{
|
||||
var w = await SemaphoreSlim.WaitAsync(60000);
|
||||
|
||||
if (!w)
|
||||
{
|
||||
throw new Exception("Unable to add torrent, could not obtain lock");
|
||||
}
|
||||
|
||||
await RealDebridUpdateLock.WaitAsync();
|
||||
|
||||
try
|
||||
{
|
||||
var torrent = await _torrentData.GetByHash(infoHash);
|
||||
|
|
@ -486,7 +553,7 @@ namespace RdtClient.Service.Services
|
|||
}
|
||||
finally
|
||||
{
|
||||
SemaphoreSlim.Release();
|
||||
RealDebridUpdateLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using RdtClient.Data.Models.Data;
|
||||
using RdtClient.Service.Helpers;
|
||||
using SharpCompress.Archives;
|
||||
using SharpCompress.Archives.Rar;
|
||||
using SharpCompress.Common;
|
||||
|
|
@ -24,7 +24,7 @@ namespace RdtClient.Service.Services
|
|||
private readonly String _destinationPath;
|
||||
private readonly Torrent _torrent;
|
||||
|
||||
private Boolean _cancelled = false;
|
||||
private Boolean _cancelled;
|
||||
|
||||
private RarArchiveEntry _rarCurrentEntry;
|
||||
private Dictionary<String, Int64> _rarfileStatus;
|
||||
|
|
@ -43,25 +43,11 @@ namespace RdtClient.Service.Services
|
|||
|
||||
try
|
||||
{
|
||||
var fileUrl = _download.Link;
|
||||
var filePath = DownloadHelper.GetDownloadPath(_destinationPath, _torrent, _download);
|
||||
|
||||
if (String.IsNullOrWhiteSpace(fileUrl))
|
||||
if (filePath == null)
|
||||
{
|
||||
throw new Exception("File URL is empty");
|
||||
}
|
||||
|
||||
var uri = new Uri(fileUrl);
|
||||
var torrentPath = Path.Combine(_destinationPath, _torrent.RdName);
|
||||
|
||||
var fileName = uri.Segments.Last();
|
||||
|
||||
fileName = HttpUtility.UrlDecode(fileName);
|
||||
|
||||
var filePath = Path.Combine(torrentPath, fileName);
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
throw new Exception($"File {filePath} could not be extracted because it is missing");
|
||||
throw new Exception("Invalid download path");
|
||||
}
|
||||
|
||||
await Task.Factory.StartNew(async delegate
|
||||
|
|
@ -88,6 +74,11 @@ namespace RdtClient.Service.Services
|
|||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await using (Stream stream = File.OpenRead(filePath))
|
||||
{
|
||||
using var archive = RarArchive.Open(stream);
|
||||
|
|
@ -97,7 +88,7 @@ namespace RdtClient.Service.Services
|
|||
var entries = archive.Entries.Where(entry => !entry.IsDirectory)
|
||||
.ToList();
|
||||
|
||||
_rarfileStatus = entries.ToDictionary(entry => entry.Key, entry => 0L);
|
||||
_rarfileStatus = entries.ToDictionary(entry => entry.Key, _ => 0L);
|
||||
_rarCurrentEntry = null;
|
||||
archive.CompressedBytesRead += ArchiveOnCompressedBytesRead;
|
||||
|
||||
|
|
@ -148,7 +139,7 @@ namespace RdtClient.Service.Services
|
|||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Error = $"An unexpected error occurred downloading {_download.Link} for torrent {_torrent.RdName}: {ex.Message}";
|
||||
Error = $"An unexpected error occurred unpacking {_download.Link} for torrent {_torrent.RdName}: {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
|
|||
|
|
@ -10,9 +10,9 @@ namespace RdtClient.Web.Controllers
|
|||
[Route("Api/Authentication")]
|
||||
public class AuthController : Controller
|
||||
{
|
||||
private readonly IAuthentication _authentication;
|
||||
private readonly Authentication _authentication;
|
||||
|
||||
public AuthController(IAuthentication authentication)
|
||||
public AuthController(Authentication authentication)
|
||||
{
|
||||
_authentication = authentication;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,9 +20,9 @@ namespace RdtClient.Web.Controllers
|
|||
[Route("api/v2")]
|
||||
public class QBittorrentController : Controller
|
||||
{
|
||||
private readonly IQBittorrent _qBittorrent;
|
||||
private readonly QBittorrent _qBittorrent;
|
||||
|
||||
public QBittorrentController(IQBittorrent qBittorrent)
|
||||
public QBittorrentController(QBittorrent qBittorrent)
|
||||
{
|
||||
_qBittorrent = qBittorrent;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,10 +15,10 @@ namespace RdtClient.Web.Controllers
|
|||
[Route("Api/Settings")]
|
||||
public class SettingsController : Controller
|
||||
{
|
||||
private readonly ISettings _settings;
|
||||
private readonly ITorrents _torrents;
|
||||
private readonly Settings _settings;
|
||||
private readonly Torrents _torrents;
|
||||
|
||||
public SettingsController(ISettings settings, ITorrents torrents)
|
||||
public SettingsController(Settings settings, Torrents torrents)
|
||||
{
|
||||
_settings = settings;
|
||||
_torrents = torrents;
|
||||
|
|
|
|||
|
|
@ -18,10 +18,10 @@ namespace RdtClient.Web.Controllers
|
|||
[Route("Api/Torrents")]
|
||||
public class TorrentsController : Controller
|
||||
{
|
||||
private readonly ITorrentRunner _torrentRunner;
|
||||
private readonly ITorrents _torrents;
|
||||
private readonly TorrentRunner _torrentRunner;
|
||||
private readonly Torrents _torrents;
|
||||
|
||||
public TorrentsController(ITorrents torrents, ITorrentRunner torrentRunner)
|
||||
public TorrentsController(Torrents torrents, TorrentRunner torrentRunner)
|
||||
{
|
||||
_torrents = torrents;
|
||||
_torrentRunner = torrentRunner;
|
||||
|
|
@ -29,7 +29,7 @@ namespace RdtClient.Web.Controllers
|
|||
|
||||
[HttpGet]
|
||||
[Route("")]
|
||||
public async Task<ActionResult<IList<Torrent>>> Get()
|
||||
public async Task<ActionResult<IList<Torrent>>> GetAll()
|
||||
{
|
||||
var results = await _torrents.Get();
|
||||
|
||||
|
|
@ -42,6 +42,23 @@ namespace RdtClient.Web.Controllers
|
|||
return Ok(results);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("Get/{torrentId:guid}")]
|
||||
public async Task<ActionResult<Torrent>> GetById(Guid torrentId)
|
||||
{
|
||||
var torrent = await _torrents.GetById(torrentId);
|
||||
|
||||
if (torrent?.Downloads != null)
|
||||
{
|
||||
foreach (var file in torrent.Downloads)
|
||||
{
|
||||
file.Torrent = null;
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(torrent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for debugging only. Force a tick.
|
||||
/// </summary>
|
||||
|
|
@ -129,19 +146,28 @@ namespace RdtClient.Web.Controllers
|
|||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("Delete/{id}")]
|
||||
public async Task<ActionResult> Delete(Guid id, [FromBody] TorrentControllerDeleteRequest request)
|
||||
[Route("Delete/{torrentId:guid}")]
|
||||
public async Task<ActionResult> Delete(Guid torrentId, [FromBody] TorrentControllerDeleteRequest request)
|
||||
{
|
||||
await _torrents.Delete(id, request.DeleteData, request.DeleteRdTorrent, request.DeleteLocalFiles);
|
||||
await _torrents.Delete(torrentId, request.DeleteData, request.DeleteRdTorrent, request.DeleteLocalFiles);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("Retry/{id}")]
|
||||
public async Task<ActionResult> Retry(Guid id, [FromBody] TorrentControllerRetryRequest request)
|
||||
[Route("Retry/{torrentId:guid}")]
|
||||
public async Task<ActionResult> Retry(Guid torrentId)
|
||||
{
|
||||
await _torrents.Retry(id, request.Retry);
|
||||
await _torrents.RetryTorrent(torrentId);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("RetryDownload/{downloadId:guid}")]
|
||||
public async Task<ActionResult> RetryDownload(Guid downloadId)
|
||||
{
|
||||
await _torrents.RetryDownload(downloadId);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
|
@ -173,11 +199,6 @@ namespace RdtClient.Web.Controllers
|
|||
public Boolean DeleteLocalFiles { get; set; }
|
||||
}
|
||||
|
||||
public class TorrentControllerRetryRequest
|
||||
{
|
||||
public Int32 Retry { get; set; }
|
||||
}
|
||||
|
||||
public class TorrentControllerCheckFilesRequest
|
||||
{
|
||||
public String MagnetLink { get; set; }
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ namespace RdtClient.Web
|
|||
Service.DiConfig.Config(services);
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<Startup> logger, ISettings settings)
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<Startup> logger, Settings settings)
|
||||
{
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue