Refactored download processor.
This commit is contained in:
parent
ec02b3d8bb
commit
a27bd37991
38 changed files with 1413 additions and 824 deletions
|
|
@ -7,6 +7,7 @@ indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
max_line_length = 120
|
||||||
|
|
||||||
[*.ts]
|
[*.ts]
|
||||||
quote_type = single
|
quote_type = single
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,58 @@
|
||||||
import { Pipe, PipeTransform } from '@angular/core';
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
import { FileSizePipe } from 'ngx-filesize';
|
||||||
import { TorrentFile } from './models/torrent.model';
|
import { TorrentFile } from './models/torrent.model';
|
||||||
import { DownloadStatus } from './models/download.model';
|
|
||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'fileStatus',
|
name: 'fileStatus',
|
||||||
})
|
})
|
||||||
export class FileStatusPipe implements PipeTransform {
|
export class FileStatusPipe implements PipeTransform {
|
||||||
|
constructor(private pipe: FileSizePipe) {}
|
||||||
|
|
||||||
transform(value: TorrentFile): string {
|
transform(value: TorrentFile): string {
|
||||||
if (
|
if (!value.download) {
|
||||||
!value.download ||
|
return 'Pending download';
|
||||||
value.download.status === DownloadStatus.PendingDownload
|
}
|
||||||
) {
|
|
||||||
|
if (value.download.error) {
|
||||||
|
return `Error: ${value.download.error}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.download.completed != null) {
|
||||||
|
return 'Finished';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.download.unpackingFinished) {
|
||||||
|
return 'Unpacking finished';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.download.unpackingStarted) {
|
||||||
|
const progress = ((value.download.bytesDone / value.download.bytesTotal) * 100).toFixed(2);
|
||||||
|
return `Unpacking ${progress || 0}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.download.unpackingQueued) {
|
||||||
|
return 'Unpacking queued';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.download.downloadFinished) {
|
||||||
|
return 'Download finished';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.download.downloadStarted) {
|
||||||
|
const progress = ((value.download.bytesDone / value.download.bytesTotal) * 100).toFixed(2);
|
||||||
|
const speed = this.pipe.transform(value.download.speed, 'filesize');
|
||||||
|
|
||||||
|
return `Downloading ${progress || 0}% (${speed}/s)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.download.downloadQueued) {
|
||||||
|
return 'Download queued';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.download.added) {
|
||||||
return 'Pending';
|
return 'Pending';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.download.status === DownloadStatus.Downloading) {
|
return '';
|
||||||
const progress = (
|
|
||||||
(value.download.bytesDownloaded / value.download.bytesSize) *
|
|
||||||
100
|
|
||||||
).toFixed(2);
|
|
||||||
return `${progress || 0}%`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value.download.status === DownloadStatus.Finished) {
|
|
||||||
return `Finished`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,17 @@
|
||||||
export class Download {
|
export class Download {
|
||||||
public downloadId: string;
|
public downloadId: string;
|
||||||
|
|
||||||
public torrentId: string;
|
public torrentId: string;
|
||||||
|
|
||||||
public link: string;
|
public link: string;
|
||||||
|
|
||||||
public added: Date;
|
public added: Date;
|
||||||
|
public downloadQueued: Date;
|
||||||
public status: DownloadStatus;
|
public downloadStarted: Date;
|
||||||
|
public downloadFinished: Date;
|
||||||
public bytesDownloaded: number;
|
public unpackingQueued: Date;
|
||||||
|
public unpackingStarted: Date;
|
||||||
public bytesSize: number;
|
public unpackingFinished: Date;
|
||||||
|
public completed: Date;
|
||||||
|
public error: string;
|
||||||
|
public bytesTotal: number;
|
||||||
|
public bytesDone: number;
|
||||||
public speed: number;
|
public speed: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum DownloadStatus {
|
|
||||||
PendingDownload = 0,
|
|
||||||
Downloading = 1,
|
|
||||||
Unpacking = 2,
|
|
||||||
Finished = 3,
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
export class Profile {
|
export class Profile {
|
||||||
userName: string;
|
public userName: string;
|
||||||
expiration: Date;
|
public expiration: Date;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,47 @@
|
||||||
import { Download } from './download.model';
|
import { Download } from './download.model';
|
||||||
|
|
||||||
export class Torrent {
|
export class Torrent {
|
||||||
torrentId: string;
|
public torrentId: string;
|
||||||
hash: string;
|
public hash: string;
|
||||||
status: TorrentStatus;
|
public category: string;
|
||||||
rdId: string;
|
public added: Date;
|
||||||
rdName: string;
|
public completed: Date;
|
||||||
rdSize: number;
|
public autoDownload: boolean;
|
||||||
rdHost: string;
|
public autoUnpack: boolean;
|
||||||
rdSplit: number;
|
public autoDelete: boolean;
|
||||||
rdProgress: number;
|
|
||||||
rdStatus: string;
|
|
||||||
rdAdded: Date;
|
|
||||||
rdEnded: Date;
|
|
||||||
rdSpeed: number;
|
|
||||||
rdSeeders: number;
|
|
||||||
rdFiles: string;
|
|
||||||
|
|
||||||
files: TorrentFile[];
|
public rdId: string;
|
||||||
downloads: Download[];
|
public rdName: string;
|
||||||
|
public rdSize: number;
|
||||||
|
public rdHost: string;
|
||||||
|
public rdSplit: number;
|
||||||
|
public rdProgress: number;
|
||||||
|
public rdStatus: RealDebridStatus;
|
||||||
|
public rdStatusRaw: string;
|
||||||
|
public rdAdded: Date;
|
||||||
|
public rdEnded: Date;
|
||||||
|
public rdSpeed: number;
|
||||||
|
public rdSeeders: number;
|
||||||
|
public rdFiles: string;
|
||||||
|
|
||||||
|
public files: TorrentFile[];
|
||||||
|
public downloads: Download[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TorrentFile {
|
export class TorrentFile {
|
||||||
id: string;
|
public id: string;
|
||||||
path: string;
|
public path: string;
|
||||||
bytes: number;
|
public bytes: number;
|
||||||
selected: boolean;
|
public selected: boolean;
|
||||||
|
|
||||||
download: Download;
|
public download: Download;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum TorrentStatus {
|
export enum RealDebridStatus {
|
||||||
RealDebrid = 0,
|
Processing = 0,
|
||||||
WaitingForDownload = 1,
|
WaitingForFileSelection = 1,
|
||||||
DownloadQueued = 2,
|
Downloading = 2,
|
||||||
Downloading = 3,
|
Finished = 3,
|
||||||
Finished = 4,
|
|
||||||
|
|
||||||
Error = 99,
|
Error = 99,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,10 @@
|
||||||
<input type="checkbox" [(ngModel)]="autoDownload" />
|
<input type="checkbox" [(ngModel)]="autoDownload" />
|
||||||
Download torrent to host when finished downloading on Real-Debrid
|
Download torrent to host when finished downloading on Real-Debrid
|
||||||
</label>
|
</label>
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox" [(ngModel)]="autoUnpack" />
|
||||||
|
Unpack files after downloading to host
|
||||||
|
</label>
|
||||||
<label class="checkbox">
|
<label class="checkbox">
|
||||||
<input type="checkbox" [(ngModel)]="autoDelete" />
|
<input type="checkbox" [(ngModel)]="autoDelete" />
|
||||||
Remove torrent when finished downloading on host
|
Remove torrent when finished downloading on host
|
||||||
|
|
@ -61,6 +65,7 @@
|
||||||
class="button is-success"
|
class="button is-success"
|
||||||
[disabled]="saving"
|
[disabled]="saving"
|
||||||
[ngClass]="{ 'is-loading': saving }"
|
[ngClass]="{ 'is-loading': saving }"
|
||||||
|
(click)="ok()"
|
||||||
>
|
>
|
||||||
<span>Add Torrent</span>
|
<span>Add Torrent</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
|
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||||
import { TorrentService } from 'src/app/torrent.service';
|
import { TorrentService } from 'src/app/torrent.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
|
@ -25,6 +25,7 @@ export class AddNewTorrentComponent implements OnInit {
|
||||||
public fileName: string;
|
public fileName: string;
|
||||||
public magnetLink: string;
|
public magnetLink: string;
|
||||||
public autoDownload: boolean;
|
public autoDownload: boolean;
|
||||||
|
public autoUnpack: boolean;
|
||||||
public autoDelete: boolean;
|
public autoDelete: boolean;
|
||||||
|
|
||||||
public saving = false;
|
public saving = false;
|
||||||
|
|
@ -67,7 +68,12 @@ export class AddNewTorrentComponent implements OnInit {
|
||||||
|
|
||||||
if (this.magnetLink) {
|
if (this.magnetLink) {
|
||||||
this.torrentService
|
this.torrentService
|
||||||
.uploadMagnet(this.magnetLink, this.autoDownload, this.autoDelete)
|
.uploadMagnet(
|
||||||
|
this.magnetLink,
|
||||||
|
this.autoDownload,
|
||||||
|
this.autoUnpack,
|
||||||
|
this.autoDelete
|
||||||
|
)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
() => {
|
() => {
|
||||||
this.cancel();
|
this.cancel();
|
||||||
|
|
@ -79,7 +85,12 @@ export class AddNewTorrentComponent implements OnInit {
|
||||||
);
|
);
|
||||||
} else if (this.selectedFile) {
|
} else if (this.selectedFile) {
|
||||||
this.torrentService
|
this.torrentService
|
||||||
.uploadFile(this.selectedFile, this.autoDownload, this.autoDelete)
|
.uploadFile(
|
||||||
|
this.selectedFile,
|
||||||
|
this.autoDownload,
|
||||||
|
this.autoUnpack,
|
||||||
|
this.autoDelete
|
||||||
|
)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
() => {
|
() => {
|
||||||
this.cancel();
|
this.cancel();
|
||||||
|
|
|
||||||
|
|
@ -42,15 +42,9 @@ export class SettingsComponent implements OnInit {
|
||||||
|
|
||||||
this.settingsService.get().subscribe(
|
this.settingsService.get().subscribe(
|
||||||
(results) => {
|
(results) => {
|
||||||
this.settingRealDebridApiKey = this.getSetting(
|
this.settingRealDebridApiKey = this.getSetting(results, 'RealDebridApiKey');
|
||||||
results,
|
|
||||||
'RealDebridApiKey'
|
|
||||||
);
|
|
||||||
this.settingDownloadFolder = this.getSetting(results, 'DownloadFolder');
|
this.settingDownloadFolder = this.getSetting(results, 'DownloadFolder');
|
||||||
this.settingDownloadLimit = parseInt(
|
this.settingDownloadLimit = parseInt(this.getSetting(results, 'DownloadLimit'), 10);
|
||||||
this.getSetting(results, 'DownloadLimit'),
|
|
||||||
10
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
this.error = err.error;
|
this.error = err.error;
|
||||||
|
|
|
||||||
|
|
@ -6,22 +6,12 @@
|
||||||
{{ torrent | status }}
|
{{ torrent | status }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span
|
<span class="icon download-icon" (click)="download($event)" title="Download to disk" *ngIf="!loading">
|
||||||
class="icon download-icon"
|
|
||||||
(click)="download($event)"
|
|
||||||
title="Download to disk"
|
|
||||||
*ngIf="!loading && torrent.status === 1"
|
|
||||||
>
|
|
||||||
<i class="fas fa-download"></i>
|
<i class="fas fa-download"></i>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span
|
<span class="icon delete-icon" (click)="delete1($event)" title="Delete torrent" *ngIf="!loading">
|
||||||
class="icon delete-icon"
|
|
||||||
(click)="delete($event)"
|
|
||||||
title="Delete torrent"
|
|
||||||
*ngIf="!loading"
|
|
||||||
>
|
|
||||||
<i class="fas fa-times"></i>
|
<i class="fas fa-times"></i>
|
||||||
</span>
|
</span>
|
||||||
<span class="icon loading-icon" *ngIf="loading">
|
<span class="icon loading-icon" *ngIf="loading">
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Component, OnInit, Input } from '@angular/core';
|
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||||
import { Torrent, TorrentStatus } from 'src/app/models/torrent.model';
|
import { Torrent } from 'src/app/models/torrent.model';
|
||||||
import { TorrentService } from 'src/app/torrent.service';
|
import { TorrentService } from 'src/app/torrent.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
|
@ -11,6 +11,9 @@ export class TorrentRowComponent implements OnInit {
|
||||||
@Input()
|
@Input()
|
||||||
public torrent: Torrent;
|
public torrent: Torrent;
|
||||||
|
|
||||||
|
@Output('delete')
|
||||||
|
public delete = new EventEmitter();
|
||||||
|
|
||||||
public loading = false;
|
public loading = false;
|
||||||
|
|
||||||
constructor(private torrentService: TorrentService) {}
|
constructor(private torrentService: TorrentService) {}
|
||||||
|
|
@ -23,14 +26,11 @@ export class TorrentRowComponent implements OnInit {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.torrentService.download(this.torrent.torrentId).subscribe(() => {
|
this.torrentService.download(this.torrent.torrentId).subscribe(() => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
this.torrent.status = TorrentStatus.Downloading;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public delete(event: Event): void {
|
public delete1(event: Event): void {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
this.delete.emit(this.torrent.torrentId);
|
||||||
this.loading = true;
|
|
||||||
this.torrentService.delete(this.torrent.torrentId).subscribe(() => {});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { Pipe, PipeTransform } from '@angular/core';
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
import { FileSizePipe } from 'ngx-filesize';
|
import { FileSizePipe } from 'ngx-filesize';
|
||||||
import { DownloadStatus } from './models/download.model';
|
import { RealDebridStatus, Torrent } from './models/torrent.model';
|
||||||
import { Torrent, TorrentStatus } from './models/torrent.model';
|
|
||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'status',
|
name: 'status',
|
||||||
|
|
@ -10,68 +9,72 @@ export class TorrentStatusPipe implements PipeTransform {
|
||||||
constructor(private pipe: FileSizePipe) {}
|
constructor(private pipe: FileSizePipe) {}
|
||||||
|
|
||||||
transform(torrent: Torrent): string {
|
transform(torrent: Torrent): string {
|
||||||
|
if (torrent.completed) {
|
||||||
|
return 'Finished';
|
||||||
|
}
|
||||||
|
|
||||||
if (torrent.downloads && torrent.downloads.length > 0) {
|
if (torrent.downloads && torrent.downloads.length > 0) {
|
||||||
const allFinished = torrent.downloads.all(
|
const allFinished = torrent.downloads.all((m) => m.completed != null);
|
||||||
(m) => m.status === DownloadStatus.Finished
|
|
||||||
);
|
|
||||||
if (allFinished) {
|
if (allFinished) {
|
||||||
return 'Finished';
|
return 'Finished';
|
||||||
}
|
}
|
||||||
|
|
||||||
const downloading = torrent.downloads.where(
|
const downloading = torrent.downloads.where((m) => m.downloadStarted && !m.downloadFinished);
|
||||||
(m) => m.status === DownloadStatus.Downloading
|
const unpacking = torrent.downloads.where((m) => m.unpackingStarted && !m.unpackingFinished);
|
||||||
);
|
|
||||||
const unpacking = torrent.downloads.where(
|
|
||||||
(m) => m.status === DownloadStatus.Unpacking
|
|
||||||
);
|
|
||||||
|
|
||||||
const allBytesDownloaded = torrent.downloads.sum(
|
let downloadText = '';
|
||||||
(m) => m.bytesDownloaded
|
let unpackText = '';
|
||||||
);
|
|
||||||
const allBytesSize = torrent.downloads.sum((m) => m.bytesSize);
|
|
||||||
|
|
||||||
let progress = 0;
|
|
||||||
let allSpeeds = 0;
|
|
||||||
|
|
||||||
if (allBytesSize > 0) {
|
|
||||||
progress = (allBytesDownloaded / allBytesSize) * 100;
|
|
||||||
allSpeeds = downloading.sum((m) => m.speed) / downloading.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
let speed: string | string[] = '0';
|
|
||||||
if (allSpeeds > 0) {
|
|
||||||
speed = this.pipe.transform(allSpeeds, 'filesize');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (downloading.length > 0) {
|
if (downloading.length > 0) {
|
||||||
if (allBytesSize > 0) {
|
const bytesDone = downloading.sum((m) => m.bytesDone);
|
||||||
return `Downloading (${progress.toFixed(2)}% - ${speed}/s)`;
|
const bytesTotal = downloading.sum((m) => m.bytesTotal);
|
||||||
}
|
let progress = (bytesDone / bytesTotal) * 100;
|
||||||
|
let allSpeeds = downloading.sum((m) => m.speed) / downloading.length;
|
||||||
|
|
||||||
return `Preparing download`;
|
let speed: string | string[] = '0';
|
||||||
|
if (allSpeeds > 0) {
|
||||||
|
speed = this.pipe.transform(allSpeeds, 'filesize');
|
||||||
|
|
||||||
|
downloadText = `Downloading (${progress.toFixed(2)}% - ${speed}/s)`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unpacking.length > 0) {
|
if (unpacking.length > 0) {
|
||||||
return `Unpacking (${progress.toFixed(2)}% - ${speed}/s)`;
|
const bytesDone = unpacking.sum((m) => m.bytesDone);
|
||||||
|
const bytesTotal = unpacking.sum((m) => m.bytesTotal);
|
||||||
|
let progress = (bytesDone / bytesTotal) * 100;
|
||||||
|
let allSpeeds = unpacking.sum((m) => m.speed) / unpacking.length;
|
||||||
|
|
||||||
|
if (allSpeeds > 0) {
|
||||||
|
downloadText = `Extracting (${progress.toFixed(2)}%)`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'Pending download';
|
let result: string[] = [];
|
||||||
|
if (downloadText) {
|
||||||
|
result.push(downloadText);
|
||||||
|
}
|
||||||
|
if (unpackText) {
|
||||||
|
result.push(unpackText);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.length > 0) {
|
||||||
|
return result.join('\r\n');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (torrent.status) {
|
switch (torrent.rdStatus) {
|
||||||
case TorrentStatus.RealDebrid:
|
case RealDebridStatus.Downloading:
|
||||||
const speed = this.pipe.transform(torrent.rdSpeed, 'filesize');
|
const speed = this.pipe.transform(torrent.rdSpeed, 'filesize');
|
||||||
return `Torrent downloading (${torrent.rdProgress}% - ${speed}/s)`;
|
return `Torrent downloading (${torrent.rdProgress}% - ${speed}/s)`;
|
||||||
case TorrentStatus.WaitingForDownload:
|
case RealDebridStatus.Processing:
|
||||||
return `Ready to download, press the download icon to start`;
|
return `Torrent processing`;
|
||||||
case TorrentStatus.DownloadQueued:
|
case RealDebridStatus.WaitingForFileSelection:
|
||||||
return `Download queued`;
|
return `Torrent waiting for file selection`;
|
||||||
case TorrentStatus.Downloading:
|
case RealDebridStatus.Error:
|
||||||
return `Downloading`;
|
return `Torrent error: ${torrent.rdStatusRaw}`;
|
||||||
case TorrentStatus.Finished:
|
case RealDebridStatus.Finished:
|
||||||
return `Finished`;
|
return `Torrent finished`;
|
||||||
case TorrentStatus.Error:
|
|
||||||
return `Error`;
|
|
||||||
default:
|
default:
|
||||||
return 'Unknown status';
|
return 'Unknown status';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,11 @@
|
||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
<table class="table is-fullwidth is-hoverable">
|
<table class="table is-fullwidth is-hoverable">
|
||||||
<colgroup>
|
<colgroup>
|
||||||
<col style="width: 50%;" />
|
<col style="width: 50%" />
|
||||||
<col style="width: 15%;" />
|
<col style="width: 15%" />
|
||||||
<col style="width: 35%;" />
|
<col style="width: 35%" />
|
||||||
<col style="width: 50px;" />
|
<col style="width: 50px" />
|
||||||
<col style="width: 50px;" />
|
<col style="width: 50px" />
|
||||||
</colgroup>
|
</colgroup>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|
@ -26,27 +26,70 @@
|
||||||
app-torrent-row
|
app-torrent-row
|
||||||
[torrent]="torrent"
|
[torrent]="torrent"
|
||||||
(click)="selectTorrent(torrent)"
|
(click)="selectTorrent(torrent)"
|
||||||
|
(delete)="showDeleteModal($event)"
|
||||||
></tr>
|
></tr>
|
||||||
|
|
||||||
<ng-container
|
<ng-container *ngIf="showFiles[torrent.torrentId] && torrent?.files.length === 0">
|
||||||
*ngIf="showFiles[torrent.torrentId] && torrent?.files.length === 0"
|
|
||||||
>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="20">
|
<td colspan="20">
|
||||||
<i class="fas fa-spinner fa-pulse"></i>
|
<i class="fas fa-spinner fa-pulse"></i>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container
|
<ng-container *ngIf="showFiles[torrent.torrentId] && torrent?.files.length > 0">
|
||||||
*ngIf="showFiles[torrent.torrentId] && torrent?.files.length > 0"
|
<tr app-torrent-file [file]="file" *ngFor="let file of torrent.files"></tr>
|
||||||
>
|
|
||||||
<tr
|
|
||||||
app-torrent-file
|
|
||||||
[file]="file"
|
|
||||||
*ngFor="let file of torrent.files"
|
|
||||||
></tr>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</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">
|
||||||
|
<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>
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,6 @@
|
||||||
import {
|
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
Component,
|
|
||||||
OnInit,
|
|
||||||
OnDestroy,
|
|
||||||
HostListener,
|
|
||||||
ElementRef,
|
|
||||||
} from '@angular/core';
|
|
||||||
import { Torrent } from '../models/torrent.model';
|
import { Torrent } from '../models/torrent.model';
|
||||||
import { TorrentService } from '../torrent.service';
|
import { TorrentService } from '../torrent.service';
|
||||||
import { DownloadStatus } from '../models/download.model';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-torrent-table',
|
selector: 'app-torrent-table',
|
||||||
|
|
@ -19,6 +12,14 @@ export class TorrentTableComponent implements OnInit, OnDestroy {
|
||||||
public error: string;
|
public error: string;
|
||||||
public showFiles: { [key: string]: boolean } = {};
|
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;
|
||||||
|
|
||||||
private timer: any;
|
private timer: any;
|
||||||
|
|
||||||
constructor(private torrentService: TorrentService) {}
|
constructor(private torrentService: TorrentService) {}
|
||||||
|
|
@ -50,9 +51,7 @@ export class TorrentTableComponent implements OnInit, OnDestroy {
|
||||||
torrent.downloads = result.downloads;
|
torrent.downloads = result.downloads;
|
||||||
|
|
||||||
torrent.files.forEach((file) => {
|
torrent.files.forEach((file) => {
|
||||||
const downloads = torrent.downloads.filter(
|
const downloads = torrent.downloads.filter((m) => m.link === file.path);
|
||||||
(m) => m.link === file.path
|
|
||||||
);
|
|
||||||
|
|
||||||
if (downloads.length > 0) {
|
if (downloads.length > 0) {
|
||||||
file.download = downloads[0];
|
file.download = downloads[0];
|
||||||
|
|
@ -65,4 +64,30 @@ export class TorrentTableComponent implements OnInit, OnDestroy {
|
||||||
public trackByMethod(index: number, el: Torrent): string {
|
public trackByMethod(index: number, el: Torrent): string {
|
||||||
return el.torrentId;
|
return el.torrentId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public showDeleteModal(torrentId: string): void {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Observable, Subject } from 'rxjs';
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
import { Torrent } from './models/torrent.model';
|
import { Torrent } from './models/torrent.model';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
|
|
@ -20,23 +20,21 @@ export class TorrentService {
|
||||||
public uploadMagnet(
|
public uploadMagnet(
|
||||||
magnetLink: string,
|
magnetLink: string,
|
||||||
autoDownload: boolean,
|
autoDownload: boolean,
|
||||||
|
autoUnpack: boolean,
|
||||||
autoDelete: boolean
|
autoDelete: boolean
|
||||||
): Observable<void> {
|
): Observable<void> {
|
||||||
return this.http.post<void>(`/Api/Torrents/UploadMagnet`, {
|
return this.http.post<void>(`/Api/Torrents/UploadMagnet`, {
|
||||||
magnetLink,
|
magnetLink,
|
||||||
autoDownload,
|
autoDownload,
|
||||||
|
autoUnpack,
|
||||||
autoDelete,
|
autoDelete,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public uploadFile(
|
public uploadFile(file: File, autoDownload: boolean, autoUnpack: boolean, autoDelete: boolean): Observable<void> {
|
||||||
file: File,
|
|
||||||
autoDownload: boolean,
|
|
||||||
autoDelete: boolean
|
|
||||||
): Observable<void> {
|
|
||||||
const formData: FormData = new FormData();
|
const formData: FormData = new FormData();
|
||||||
formData.append('file', file);
|
formData.append('file', file);
|
||||||
formData.append('formData', JSON.stringify({ autoDownload, autoDelete }));
|
formData.append('formData', JSON.stringify({ autoDownload, autoUnpack, autoDelete }));
|
||||||
return this.http.post<void>(`/Api/Torrents/UploadFile`, formData);
|
return this.http.post<void>(`/Api/Torrents/UploadFile`, formData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -44,7 +42,16 @@ export class TorrentService {
|
||||||
return this.http.get<void>(`/Api/Torrents/Download/${torrentId}`);
|
return this.http.get<void>(`/Api/Torrents/Download/${torrentId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
public delete(torrentId: string): Observable<void> {
|
public delete(
|
||||||
return this.http.delete<void>(`/Api/Torrents/${torrentId}`);
|
torrentId: string,
|
||||||
|
deleteData: boolean,
|
||||||
|
deleteRdTorrent: boolean,
|
||||||
|
deleteLocalFiles: boolean
|
||||||
|
): Observable<void> {
|
||||||
|
return this.http.post<void>(`/Api/Torrents/Delete/${torrentId}`, {
|
||||||
|
deleteData,
|
||||||
|
deleteRdTorrent,
|
||||||
|
deleteLocalFiles,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,11 @@ namespace RdtClient.Data.Data
|
||||||
{
|
{
|
||||||
SettingId = "DownloadFolder",
|
SettingId = "DownloadFolder",
|
||||||
Type = "String",
|
Type = "String",
|
||||||
|
#if DEBUG
|
||||||
|
Value = @"C:\Temp\rdtclient"
|
||||||
|
#else
|
||||||
Value = "/data/downloads"
|
Value = "/data/downloads"
|
||||||
|
#endif
|
||||||
},
|
},
|
||||||
new Setting
|
new Setting
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using RdtClient.Data.Enums;
|
|
||||||
using RdtClient.Data.Models.Data;
|
using RdtClient.Data.Models.Data;
|
||||||
|
|
||||||
namespace RdtClient.Data.Data
|
namespace RdtClient.Data.Data
|
||||||
|
|
@ -12,8 +11,14 @@ namespace RdtClient.Data.Data
|
||||||
{
|
{
|
||||||
Task<IList<Download>> Get();
|
Task<IList<Download>> Get();
|
||||||
Task<IList<Download>> GetForTorrent(Guid torrentId);
|
Task<IList<Download>> GetForTorrent(Guid torrentId);
|
||||||
|
Task<Download> GetById(Guid downloadId);
|
||||||
Task<Download> Add(Guid torrentId, String link);
|
Task<Download> Add(Guid torrentId, String link);
|
||||||
Task UpdateStatus(Guid downloadId, DownloadStatus status);
|
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 UpdateError(Guid downloadId, String error);
|
||||||
Task DeleteForTorrent(Guid torrentId);
|
Task DeleteForTorrent(Guid torrentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,6 +47,14 @@ namespace RdtClient.Data.Data
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<Download> GetById(Guid downloadId)
|
||||||
|
{
|
||||||
|
return await _dataContext.Downloads
|
||||||
|
.Include(m => m.Torrent)
|
||||||
|
.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<Download> Add(Guid torrentId, String link)
|
public async Task<Download> Add(Guid torrentId, String link)
|
||||||
{
|
{
|
||||||
var download = new Download
|
var download = new Download
|
||||||
|
|
@ -50,7 +63,7 @@ namespace RdtClient.Data.Data
|
||||||
TorrentId = torrentId,
|
TorrentId = torrentId,
|
||||||
Link = link,
|
Link = link,
|
||||||
Added = DateTimeOffset.UtcNow,
|
Added = DateTimeOffset.UtcNow,
|
||||||
Status = DownloadStatus.PendingDownload
|
DownloadQueued = DateTimeOffset.UtcNow
|
||||||
};
|
};
|
||||||
|
|
||||||
await _dataContext.Downloads.AddAsync(download);
|
await _dataContext.Downloads.AddAsync(download);
|
||||||
|
|
@ -59,12 +72,64 @@ namespace RdtClient.Data.Data
|
||||||
|
|
||||||
return download;
|
return download;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateStatus(Guid downloadId, DownloadStatus status)
|
public async Task UpdateDownloadStarted(Guid downloadId, DateTimeOffset? dateTime)
|
||||||
{
|
{
|
||||||
var download = await _dataContext.Downloads.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
var dbDownload = await _dataContext.Downloads
|
||||||
|
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||||
|
|
||||||
download.Status = status;
|
dbDownload.DownloadStarted = dateTime;
|
||||||
|
|
||||||
|
await _dataContext.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateDownloadFinished(Guid downloadId, DateTimeOffset? dateTime)
|
||||||
|
{
|
||||||
|
var dbDownload = await _dataContext.Downloads
|
||||||
|
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||||
|
|
||||||
|
dbDownload.DownloadFinished = dateTime;
|
||||||
|
|
||||||
|
await _dataContext.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateUnpackingQueued(Guid downloadId, DateTimeOffset? dateTime)
|
||||||
|
{
|
||||||
|
var dbDownload = await _dataContext.Downloads
|
||||||
|
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||||
|
|
||||||
|
dbDownload.UnpackingQueued = dateTime;
|
||||||
|
|
||||||
|
await _dataContext.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateUnpackingStarted(Guid downloadId, DateTimeOffset? dateTime)
|
||||||
|
{
|
||||||
|
var dbDownload = await _dataContext.Downloads
|
||||||
|
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||||
|
|
||||||
|
dbDownload.UnpackingStarted = dateTime;
|
||||||
|
|
||||||
|
await _dataContext.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateUnpackingFinished(Guid downloadId, DateTimeOffset? dateTime)
|
||||||
|
{
|
||||||
|
var dbDownload = await _dataContext.Downloads
|
||||||
|
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||||
|
|
||||||
|
dbDownload.UnpackingFinished = dateTime;
|
||||||
|
|
||||||
|
await _dataContext.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateError(Guid downloadId, String error)
|
||||||
|
{
|
||||||
|
var dbDownload = await _dataContext.Downloads
|
||||||
|
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||||
|
|
||||||
|
dbDownload.Completed = DateTimeOffset.UtcNow;
|
||||||
|
dbDownload.Error = error;
|
||||||
|
|
||||||
await _dataContext.SaveChangesAsync();
|
await _dataContext.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using RdtClient.Data.Enums;
|
|
||||||
using RdtClient.Data.Models.Data;
|
using RdtClient.Data.Models.Data;
|
||||||
|
|
||||||
namespace RdtClient.Data.Data
|
namespace RdtClient.Data.Data
|
||||||
|
|
@ -11,13 +9,13 @@ namespace RdtClient.Data.Data
|
||||||
public interface ITorrentData
|
public interface ITorrentData
|
||||||
{
|
{
|
||||||
Task<IList<Torrent>> Get();
|
Task<IList<Torrent>> Get();
|
||||||
Task<Torrent> GetById(Guid id);
|
Task<Torrent> GetById(Guid torrentId);
|
||||||
Task<Torrent> GetByHash(String hash);
|
Task<Torrent> GetByHash(String hash);
|
||||||
Task<Torrent> Add(String realDebridId, String hash, Boolean autoDownload, Boolean autoDelete);
|
Task<Torrent> Add(String realDebridId, String hash, Boolean autoDownload, Boolean autoUnpack, Boolean autoDelete);
|
||||||
Task UpdateRdData(Torrent torrent);
|
Task UpdateRdData(Torrent torrent);
|
||||||
Task UpdateStatus(Guid torrentId, TorrentStatus status);
|
|
||||||
Task UpdateCategory(Guid torrentId, String category);
|
Task UpdateCategory(Guid torrentId, String category);
|
||||||
Task Delete(Guid id);
|
Task UpdateComplete(Guid torrentId, DateTimeOffset datetime);
|
||||||
|
Task Delete(Guid torrentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TorrentData : ITorrentData
|
public class TorrentData : ITorrentData
|
||||||
|
|
@ -36,20 +34,15 @@ namespace RdtClient.Data.Data
|
||||||
.Include(m => m.Downloads)
|
.Include(m => m.Downloads)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
foreach (var file in results.SelectMany(torrent => torrent.Downloads))
|
|
||||||
{
|
|
||||||
file.Torrent = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Torrent> GetById(Guid id)
|
public async Task<Torrent> GetById(Guid torrentId)
|
||||||
{
|
{
|
||||||
var dbTorrent = await _dataContext.Torrents
|
var dbTorrent = await _dataContext.Torrents
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Include(m => m.Downloads)
|
.Include(m => m.Downloads)
|
||||||
.FirstOrDefaultAsync(m => m.TorrentId == id);
|
.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
|
||||||
|
|
||||||
if (dbTorrent == null)
|
if (dbTorrent == null)
|
||||||
{
|
{
|
||||||
|
|
@ -84,15 +77,16 @@ namespace RdtClient.Data.Data
|
||||||
return dbTorrent;
|
return dbTorrent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Torrent> Add(String realDebridId, String hash, Boolean autoDownload, Boolean autoDelete)
|
public async Task<Torrent> Add(String realDebridId, String hash, Boolean autoDownload, Boolean autoUnpack, Boolean autoDelete)
|
||||||
{
|
{
|
||||||
var torrent = new Torrent
|
var torrent = new Torrent
|
||||||
{
|
{
|
||||||
TorrentId = Guid.NewGuid(),
|
TorrentId = Guid.NewGuid(),
|
||||||
|
Added = DateTimeOffset.UtcNow,
|
||||||
RdId = realDebridId,
|
RdId = realDebridId,
|
||||||
Hash = hash.ToLower(),
|
Hash = hash.ToLower(),
|
||||||
Status = TorrentStatus.RealDebrid,
|
|
||||||
AutoDownload = autoDownload,
|
AutoDownload = autoDownload,
|
||||||
|
AutoUnpack = autoUnpack,
|
||||||
AutoDelete = autoDelete
|
AutoDelete = autoDelete
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -118,6 +112,7 @@ namespace RdtClient.Data.Data
|
||||||
dbTorrent.RdSplit = torrent.RdSplit;
|
dbTorrent.RdSplit = torrent.RdSplit;
|
||||||
dbTorrent.RdProgress = torrent.RdProgress;
|
dbTorrent.RdProgress = torrent.RdProgress;
|
||||||
dbTorrent.RdStatus = torrent.RdStatus;
|
dbTorrent.RdStatus = torrent.RdStatus;
|
||||||
|
dbTorrent.RdStatusRaw = torrent.RdStatusRaw;
|
||||||
dbTorrent.RdAdded = torrent.RdAdded;
|
dbTorrent.RdAdded = torrent.RdAdded;
|
||||||
dbTorrent.RdEnded = torrent.RdEnded;
|
dbTorrent.RdEnded = torrent.RdEnded;
|
||||||
dbTorrent.RdSpeed = torrent.RdSpeed;
|
dbTorrent.RdSpeed = torrent.RdSpeed;
|
||||||
|
|
@ -130,21 +125,7 @@ namespace RdtClient.Data.Data
|
||||||
|
|
||||||
await _dataContext.SaveChangesAsync();
|
await _dataContext.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateStatus(Guid torrentId, TorrentStatus status)
|
|
||||||
{
|
|
||||||
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
|
|
||||||
|
|
||||||
if (dbTorrent == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
dbTorrent.Status = status;
|
|
||||||
|
|
||||||
await _dataContext.SaveChangesAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task UpdateCategory(Guid torrentId, String category)
|
public async Task UpdateCategory(Guid torrentId, String category)
|
||||||
{
|
{
|
||||||
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
|
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
|
||||||
|
|
@ -159,9 +140,18 @@ namespace RdtClient.Data.Data
|
||||||
await _dataContext.SaveChangesAsync();
|
await _dataContext.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Delete(Guid id)
|
public async Task UpdateComplete(Guid torrentId, DateTimeOffset datetime)
|
||||||
{
|
{
|
||||||
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == id);
|
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
|
||||||
|
|
||||||
|
dbTorrent.Completed = datetime;
|
||||||
|
|
||||||
|
await _dataContext.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Delete(Guid torrentId)
|
||||||
|
{
|
||||||
|
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
|
||||||
|
|
||||||
if (dbTorrent == null)
|
if (dbTorrent == null)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
namespace RdtClient.Data.Enums
|
|
||||||
{
|
|
||||||
public enum DownloadStatus
|
|
||||||
{
|
|
||||||
PendingDownload = 0,
|
|
||||||
Downloading,
|
|
||||||
Unpacking,
|
|
||||||
Finished
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
namespace RdtClient.Data.Enums
|
namespace RdtClient.Data.Enums
|
||||||
{
|
{
|
||||||
public enum TorrentStatus
|
public enum RealDebridStatus
|
||||||
{
|
{
|
||||||
RealDebrid = 0,
|
Processing = 0,
|
||||||
WaitingForDownload = 1,
|
WaitingForFileSelection = 1,
|
||||||
DownloadQueued = 2,
|
Downloading = 2,
|
||||||
Downloading = 3,
|
Finished = 3,
|
||||||
Finished = 4,
|
|
||||||
|
|
||||||
Error = 99
|
Error = 99
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ using RdtClient.Data.Data;
|
||||||
namespace RdtClient.Data.Migrations
|
namespace RdtClient.Data.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(DataContext))]
|
[DbContext(typeof(DataContext))]
|
||||||
[Migration("20201212204128_Initial")]
|
[Migration("20210110025102_Initial")]
|
||||||
partial class Initial
|
partial class Initial
|
||||||
{
|
{
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
|
@ -219,15 +219,36 @@ namespace RdtClient.Data.Migrations
|
||||||
b.Property<DateTimeOffset>("Added")
|
b.Property<DateTimeOffset>("Added")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("Completed")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("DownloadFinished")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("DownloadQueued")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("DownloadStarted")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Error")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<string>("Link")
|
b.Property<string>("Link")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<int>("Status")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<Guid>("TorrentId")
|
b.Property<Guid>("TorrentId")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("UnpackingFinished")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("UnpackingQueued")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("UnpackingStarted")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.HasKey("DownloadId");
|
b.HasKey("DownloadId");
|
||||||
|
|
||||||
b.HasIndex("TorrentId");
|
b.HasIndex("TorrentId");
|
||||||
|
|
@ -257,15 +278,24 @@ namespace RdtClient.Data.Migrations
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("Added")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<bool>("AutoDelete")
|
b.Property<bool>("AutoDelete")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<bool>("AutoDownload")
|
b.Property<bool>("AutoDownload")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("AutoUnpack")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<string>("Category")
|
b.Property<string>("Category")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("Completed")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<string>("Hash")
|
b.Property<string>("Hash")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
|
@ -302,12 +332,12 @@ namespace RdtClient.Data.Migrations
|
||||||
b.Property<long>("RdSplit")
|
b.Property<long>("RdSplit")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<string>("RdStatus")
|
b.Property<int>("RdStatus")
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<int>("Status")
|
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("RdStatusRaw")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.HasKey("TorrentId");
|
b.HasKey("TorrentId");
|
||||||
|
|
||||||
b.ToTable("Torrents");
|
b.ToTable("Torrents");
|
||||||
|
|
@ -66,16 +66,19 @@ namespace RdtClient.Data.Migrations
|
||||||
TorrentId = table.Column<Guid>(type: "TEXT", nullable: false),
|
TorrentId = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||||
Hash = table.Column<string>(type: "TEXT", nullable: true),
|
Hash = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
Category = table.Column<string>(type: "TEXT", nullable: true),
|
Category = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
Added = table.Column<DateTimeOffset>(type: "TEXT", nullable: false),
|
||||||
|
Completed = table.Column<DateTimeOffset>(type: "TEXT", nullable: true),
|
||||||
AutoDownload = table.Column<bool>(type: "INTEGER", nullable: false),
|
AutoDownload = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||||
|
AutoUnpack = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||||
AutoDelete = table.Column<bool>(type: "INTEGER", nullable: false),
|
AutoDelete = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||||
Status = table.Column<int>(type: "INTEGER", nullable: false),
|
|
||||||
RdId = table.Column<string>(type: "TEXT", nullable: true),
|
RdId = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
RdName = table.Column<string>(type: "TEXT", nullable: true),
|
RdName = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
RdSize = table.Column<long>(type: "INTEGER", nullable: false),
|
RdSize = table.Column<long>(type: "INTEGER", nullable: false),
|
||||||
RdHost = table.Column<string>(type: "TEXT", nullable: true),
|
RdHost = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
RdSplit = table.Column<long>(type: "INTEGER", nullable: false),
|
RdSplit = table.Column<long>(type: "INTEGER", nullable: false),
|
||||||
RdProgress = table.Column<long>(type: "INTEGER", nullable: false),
|
RdProgress = table.Column<long>(type: "INTEGER", nullable: false),
|
||||||
RdStatus = table.Column<string>(type: "TEXT", nullable: true),
|
RdStatus = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
RdStatusRaw = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
RdAdded = table.Column<DateTimeOffset>(type: "TEXT", nullable: false),
|
RdAdded = table.Column<DateTimeOffset>(type: "TEXT", nullable: false),
|
||||||
RdEnded = table.Column<DateTimeOffset>(type: "TEXT", nullable: true),
|
RdEnded = table.Column<DateTimeOffset>(type: "TEXT", nullable: true),
|
||||||
RdSpeed = table.Column<long>(type: "INTEGER", nullable: true),
|
RdSpeed = table.Column<long>(type: "INTEGER", nullable: true),
|
||||||
|
|
@ -201,7 +204,14 @@ namespace RdtClient.Data.Migrations
|
||||||
TorrentId = table.Column<Guid>(type: "TEXT", nullable: false),
|
TorrentId = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||||
Link = table.Column<string>(type: "TEXT", nullable: true),
|
Link = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
Added = table.Column<DateTimeOffset>(type: "TEXT", nullable: false),
|
Added = table.Column<DateTimeOffset>(type: "TEXT", nullable: false),
|
||||||
Status = table.Column<int>(type: "INTEGER", nullable: false)
|
DownloadQueued = table.Column<DateTimeOffset>(type: "TEXT", nullable: true),
|
||||||
|
DownloadStarted = table.Column<DateTimeOffset>(type: "TEXT", nullable: true),
|
||||||
|
DownloadFinished = table.Column<DateTimeOffset>(type: "TEXT", nullable: true),
|
||||||
|
UnpackingQueued = table.Column<DateTimeOffset>(type: "TEXT", nullable: true),
|
||||||
|
UnpackingStarted = table.Column<DateTimeOffset>(type: "TEXT", nullable: true),
|
||||||
|
UnpackingFinished = table.Column<DateTimeOffset>(type: "TEXT", nullable: true),
|
||||||
|
Completed = table.Column<DateTimeOffset>(type: "TEXT", nullable: true),
|
||||||
|
Error = table.Column<string>(type: "TEXT", nullable: true)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
|
|
@ -217,15 +217,36 @@ namespace RdtClient.Data.Migrations
|
||||||
b.Property<DateTimeOffset>("Added")
|
b.Property<DateTimeOffset>("Added")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("Completed")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("DownloadFinished")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("DownloadQueued")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("DownloadStarted")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Error")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<string>("Link")
|
b.Property<string>("Link")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<int>("Status")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<Guid>("TorrentId")
|
b.Property<Guid>("TorrentId")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("UnpackingFinished")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("UnpackingQueued")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("UnpackingStarted")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.HasKey("DownloadId");
|
b.HasKey("DownloadId");
|
||||||
|
|
||||||
b.HasIndex("TorrentId");
|
b.HasIndex("TorrentId");
|
||||||
|
|
@ -255,15 +276,24 @@ namespace RdtClient.Data.Migrations
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("Added")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<bool>("AutoDelete")
|
b.Property<bool>("AutoDelete")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<bool>("AutoDownload")
|
b.Property<bool>("AutoDownload")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("AutoUnpack")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<string>("Category")
|
b.Property<string>("Category")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("Completed")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<string>("Hash")
|
b.Property<string>("Hash")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
|
@ -300,12 +330,12 @@ namespace RdtClient.Data.Migrations
|
||||||
b.Property<long>("RdSplit")
|
b.Property<long>("RdSplit")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<string>("RdStatus")
|
b.Property<int>("RdStatus")
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<int>("Status")
|
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("RdStatusRaw")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.HasKey("TorrentId");
|
b.HasKey("TorrentId");
|
||||||
|
|
||||||
b.ToTable("Torrents");
|
b.ToTable("Torrents");
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using RdtClient.Data.Enums;
|
|
||||||
|
|
||||||
namespace RdtClient.Data.Models.Data
|
namespace RdtClient.Data.Models.Data
|
||||||
{
|
{
|
||||||
|
|
@ -15,17 +14,31 @@ namespace RdtClient.Data.Models.Data
|
||||||
public String Link { get; set; }
|
public String Link { get; set; }
|
||||||
|
|
||||||
public DateTimeOffset Added { get; set; }
|
public DateTimeOffset Added { get; set; }
|
||||||
|
|
||||||
|
public DateTimeOffset? DownloadQueued { get; set; }
|
||||||
|
|
||||||
public DownloadStatus Status { get; set; }
|
public DateTimeOffset? DownloadStarted { get; set; }
|
||||||
|
|
||||||
|
public DateTimeOffset? DownloadFinished { get; set; }
|
||||||
|
|
||||||
|
public DateTimeOffset? UnpackingQueued { get; set; }
|
||||||
|
|
||||||
|
public DateTimeOffset? UnpackingStarted { get; set; }
|
||||||
|
|
||||||
|
public DateTimeOffset? UnpackingFinished { get; set; }
|
||||||
|
|
||||||
|
public DateTimeOffset? Completed { get; set; }
|
||||||
|
|
||||||
|
public String Error { get; set; }
|
||||||
|
|
||||||
[ForeignKey("TorrentId")]
|
[ForeignKey("TorrentId")]
|
||||||
public Torrent Torrent { get; set; }
|
public Torrent Torrent { get; set; }
|
||||||
|
|
||||||
[NotMapped]
|
[NotMapped]
|
||||||
public Int64 BytesSize { get; set; }
|
public Int64 BytesTotal { get; set; }
|
||||||
|
|
||||||
[NotMapped]
|
[NotMapped]
|
||||||
public Int64 BytesDownloaded { get; set; }
|
public Int64 BytesDone { get; set; }
|
||||||
|
|
||||||
[NotMapped]
|
[NotMapped]
|
||||||
public Int64 Speed { get; set; }
|
public Int64 Speed { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -16,13 +16,14 @@ namespace RdtClient.Data.Models.Data
|
||||||
public String Hash { get; set; }
|
public String Hash { get; set; }
|
||||||
|
|
||||||
public String Category { get; set; }
|
public String Category { get; set; }
|
||||||
|
|
||||||
|
public DateTimeOffset Added { get; set; }
|
||||||
|
public DateTimeOffset? Completed { get; set; }
|
||||||
|
|
||||||
public Boolean AutoDownload { get; set; }
|
public Boolean AutoDownload { get; set; }
|
||||||
|
public Boolean AutoUnpack { get; set; }
|
||||||
public Boolean AutoDelete { get; set; }
|
public Boolean AutoDelete { get; set; }
|
||||||
|
|
||||||
public TorrentStatus Status { get; set; }
|
|
||||||
|
|
||||||
[InverseProperty("Torrent")]
|
[InverseProperty("Torrent")]
|
||||||
public IList<Download> Downloads { get; set; }
|
public IList<Download> Downloads { get; set; }
|
||||||
|
|
||||||
|
|
@ -32,7 +33,8 @@ namespace RdtClient.Data.Models.Data
|
||||||
public String RdHost { get; set; }
|
public String RdHost { get; set; }
|
||||||
public Int64 RdSplit { get; set; }
|
public Int64 RdSplit { get; set; }
|
||||||
public Int64 RdProgress { get; set; }
|
public Int64 RdProgress { get; set; }
|
||||||
public String RdStatus { get; set; }
|
public RealDebridStatus RdStatus { get; set; }
|
||||||
|
public String RdStatusRaw { get; set; }
|
||||||
public DateTimeOffset RdAdded { get; set; }
|
public DateTimeOffset RdAdded { get; set; }
|
||||||
public DateTimeOffset? RdEnded { get; set; }
|
public DateTimeOffset? RdEnded { get; set; }
|
||||||
public Int64? RdSpeed { get; set; }
|
public Int64? RdSpeed { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ namespace RdtClient.Service
|
||||||
services.AddScoped<IQBittorrent, QBittorrent>();
|
services.AddScoped<IQBittorrent, QBittorrent>();
|
||||||
services.AddScoped<ISettings, Settings>();
|
services.AddScoped<ISettings, Settings>();
|
||||||
services.AddScoped<ITorrents, Torrents>();
|
services.AddScoped<ITorrents, Torrents>();
|
||||||
|
services.AddScoped<ITorrentRunner, TorrentRunner>();
|
||||||
|
services.AddScoped<ITorrentDownloadManager, TorrentDownloadManager>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,5 +2,6 @@
|
||||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<ActiveDebugProfile>IIS Express</ActiveDebugProfile>
|
<ActiveDebugProfile>IIS Express</ActiveDebugProfile>
|
||||||
|
<ShowAllFiles>false</ShowAllFiles>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
138
server/RdtClient.Service/Services/DownloadClient.cs
Normal file
138
server/RdtClient.Service/Services/DownloadClient.cs
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using RdtClient.Data.Models.Data;
|
||||||
|
|
||||||
|
namespace RdtClient.Service.Services
|
||||||
|
{
|
||||||
|
public class DownloadClient
|
||||||
|
{
|
||||||
|
public Boolean Finished { get; private set; }
|
||||||
|
|
||||||
|
public String Error { get; private set; }
|
||||||
|
|
||||||
|
public Int64 Speed { get; private set; }
|
||||||
|
public Int64 BytesTotal { get; private set; }
|
||||||
|
public Int64 BytesDone { get; private set; }
|
||||||
|
|
||||||
|
private readonly Download _download;
|
||||||
|
private readonly String _destinationPath;
|
||||||
|
private readonly Torrent _torrent;
|
||||||
|
|
||||||
|
private Int64 _bytesLastUpdate;
|
||||||
|
private DateTime _nextUpdate;
|
||||||
|
|
||||||
|
public DownloadClient(Download download, String destinationPath)
|
||||||
|
{
|
||||||
|
_download = download;
|
||||||
|
_destinationPath = destinationPath;
|
||||||
|
_torrent = download.Torrent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Start()
|
||||||
|
{
|
||||||
|
BytesDone = 0;
|
||||||
|
BytesTotal = 0;
|
||||||
|
Speed = 0;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
var filePath = Path.Combine(torrentPath, fileName);
|
||||||
|
|
||||||
|
if (File.Exists(filePath))
|
||||||
|
{
|
||||||
|
File.Delete(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Factory.StartNew(async delegate
|
||||||
|
{
|
||||||
|
await Download(uri, filePath);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Error = $"An unexpected error occurred preparing download {_download.Link} for torrent {_torrent.RdName}: {ex.Message}";
|
||||||
|
Finished = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Download(Uri uri, String filePath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_bytesLastUpdate = 0;
|
||||||
|
_nextUpdate = DateTime.UtcNow.AddSeconds(1);
|
||||||
|
|
||||||
|
// Determine the file size
|
||||||
|
var webRequest = WebRequest.Create(uri);
|
||||||
|
webRequest.Method = "HEAD";
|
||||||
|
Int64 responseLength;
|
||||||
|
|
||||||
|
using (var webResponse = await webRequest.GetResponseAsync())
|
||||||
|
{
|
||||||
|
responseLength = Int64.Parse(webResponse.Headers.Get("Content-Length"));
|
||||||
|
}
|
||||||
|
|
||||||
|
var request = WebRequest.Create(uri);
|
||||||
|
using var response = await request.GetResponseAsync();
|
||||||
|
|
||||||
|
await using var stream = response.GetResponseStream();
|
||||||
|
await using var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Write);
|
||||||
|
var buffer = new Byte[4096];
|
||||||
|
|
||||||
|
while (fileStream.Length < response.ContentLength)
|
||||||
|
{
|
||||||
|
var read = await stream.ReadAsync(buffer, 0, buffer.Length);
|
||||||
|
|
||||||
|
if (read > 0)
|
||||||
|
{
|
||||||
|
fileStream.Write(buffer, 0, read);
|
||||||
|
|
||||||
|
BytesDone = fileStream.Length;
|
||||||
|
BytesTotal = responseLength;
|
||||||
|
|
||||||
|
if (DateTime.UtcNow > _nextUpdate)
|
||||||
|
{
|
||||||
|
Speed = fileStream.Length - _bytesLastUpdate;
|
||||||
|
|
||||||
|
_nextUpdate = DateTime.UtcNow.AddSeconds(1);
|
||||||
|
_bytesLastUpdate = fileStream.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Speed = 0;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Error = $"An unexpected error occurred downloading {_download.Link} for torrent {_torrent.RdName}: {ex.Message}";
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Finished = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,206 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using RdtClient.Data.Enums;
|
|
||||||
using RdtClient.Data.Models.Data;
|
|
||||||
using SharpCompress.Archives;
|
|
||||||
using SharpCompress.Archives.Rar;
|
|
||||||
using SharpCompress.Common;
|
|
||||||
|
|
||||||
namespace RdtClient.Service.Services
|
|
||||||
{
|
|
||||||
public class DownloadManager
|
|
||||||
{
|
|
||||||
private Int64 _bytesLastUpdate;
|
|
||||||
|
|
||||||
private DateTime _nextUpdate;
|
|
||||||
|
|
||||||
private RarArchiveEntry _rarCurrentEntry;
|
|
||||||
private Dictionary<String, Int64> _rarfileStatus;
|
|
||||||
|
|
||||||
public DownloadManager()
|
|
||||||
{
|
|
||||||
ServicePointManager.Expect100Continue = false;
|
|
||||||
ServicePointManager.DefaultConnectionLimit = 100;
|
|
||||||
ServicePointManager.MaxServicePointIdleTime = 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DownloadStatus? NewStatus { get; set; }
|
|
||||||
public Download Download { get; set; }
|
|
||||||
public Int64 Speed { get; private set; }
|
|
||||||
public Int64 BytesDownloaded { get; private set; }
|
|
||||||
public Int64 BytesSize { get; private set; }
|
|
||||||
|
|
||||||
private DownloadManager ActiveDownload => TaskRunner.ActiveDownloads[Download.DownloadId];
|
|
||||||
|
|
||||||
public async Task Start(String destinationFolderPath, String torrentName, DownloadStatus status)
|
|
||||||
{
|
|
||||||
ActiveDownload.BytesDownloaded = 0;
|
|
||||||
ActiveDownload.BytesSize = 0;
|
|
||||||
ActiveDownload.Speed = 0;
|
|
||||||
|
|
||||||
var fileUrl = Download.Link;
|
|
||||||
|
|
||||||
var uri = new Uri(fileUrl);
|
|
||||||
var torrentPath = Path.Combine(destinationFolderPath, torrentName);
|
|
||||||
var filePath = Path.Combine(torrentPath, uri.Segments.Last());
|
|
||||||
|
|
||||||
if (status == DownloadStatus.Unpacking)
|
|
||||||
{
|
|
||||||
await Extract(filePath, destinationFolderPath, torrentName);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ActiveDownload.NewStatus = DownloadStatus.Downloading;
|
|
||||||
|
|
||||||
_bytesLastUpdate = 0;
|
|
||||||
_nextUpdate = DateTime.UtcNow.AddSeconds(1);
|
|
||||||
|
|
||||||
if (!Directory.Exists(torrentPath))
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(torrentPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
var webRequest = WebRequest.Create(fileUrl);
|
|
||||||
webRequest.Method = "HEAD";
|
|
||||||
Int64 responseLength;
|
|
||||||
using (var webResponse = await webRequest.GetResponseAsync())
|
|
||||||
{
|
|
||||||
responseLength = Int64.Parse(webResponse.Headers.Get("Content-Length"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (File.Exists(filePath))
|
|
||||||
{
|
|
||||||
File.Delete(filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
var request = WebRequest.Create(fileUrl);
|
|
||||||
using (var response = await request.GetResponseAsync())
|
|
||||||
{
|
|
||||||
await using var stream = response.GetResponseStream();
|
|
||||||
await using var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Write);
|
|
||||||
var buffer = new Byte[4096];
|
|
||||||
|
|
||||||
while (fileStream.Length < response.ContentLength)
|
|
||||||
{
|
|
||||||
var read = await stream.ReadAsync(buffer, 0, buffer.Length);
|
|
||||||
|
|
||||||
if (read > 0)
|
|
||||||
{
|
|
||||||
fileStream.Write(buffer, 0, read);
|
|
||||||
|
|
||||||
ActiveDownload.BytesDownloaded = fileStream.Length;
|
|
||||||
ActiveDownload.BytesSize = responseLength;
|
|
||||||
|
|
||||||
if (DateTime.UtcNow > _nextUpdate)
|
|
||||||
{
|
|
||||||
ActiveDownload.Speed = fileStream.Length - _bytesLastUpdate;
|
|
||||||
|
|
||||||
_nextUpdate = DateTime.UtcNow.AddSeconds(1);
|
|
||||||
_bytesLastUpdate = fileStream.Length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ActiveDownload.Speed = 0;
|
|
||||||
ActiveDownload.BytesDownloaded = ActiveDownload.BytesSize;
|
|
||||||
|
|
||||||
await Extract(filePath, destinationFolderPath, torrentName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task Extract(String filePath, String destinationFolderPath, String torrentName)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (filePath.EndsWith(".rar"))
|
|
||||||
{
|
|
||||||
ActiveDownload.NewStatus = DownloadStatus.Unpacking;
|
|
||||||
|
|
||||||
await using (Stream stream = File.OpenRead(filePath))
|
|
||||||
{
|
|
||||||
using var archive = RarArchive.Open(stream);
|
|
||||||
|
|
||||||
ActiveDownload.BytesSize = archive.TotalSize;
|
|
||||||
|
|
||||||
var entries = archive.Entries.Where(entry => !entry.IsDirectory)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
_rarfileStatus = entries.ToDictionary(entry => entry.Key, entry => 0L);
|
|
||||||
_rarCurrentEntry = null;
|
|
||||||
archive.CompressedBytesRead += ArchiveOnCompressedBytesRead;
|
|
||||||
|
|
||||||
var extractPath = destinationFolderPath;
|
|
||||||
|
|
||||||
if (!entries.Any(m => m.Key.StartsWith(torrentName + @"\")) && !entries.Any(m => m.Key.StartsWith(torrentName + @"/")))
|
|
||||||
{
|
|
||||||
extractPath = Path.Combine(destinationFolderPath, torrentName);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entries.Any(m => m.Key.Contains(".r00")))
|
|
||||||
{
|
|
||||||
extractPath = Path.Combine(extractPath, "Temp");
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var entry in entries)
|
|
||||||
{
|
|
||||||
_rarCurrentEntry = entry;
|
|
||||||
|
|
||||||
entry.WriteToDirectory(extractPath,
|
|
||||||
new ExtractionOptions
|
|
||||||
{
|
|
||||||
ExtractFullPath = true,
|
|
||||||
Overwrite = true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var retryCount = 0;
|
|
||||||
while (File.Exists(filePath) && retryCount < 10)
|
|
||||||
{
|
|
||||||
retryCount++;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
File.Delete(filePath);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
await Task.Delay(1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
|
|
||||||
ActiveDownload.Speed = 0;
|
|
||||||
ActiveDownload.BytesDownloaded = ActiveDownload.BytesSize;
|
|
||||||
ActiveDownload.NewStatus = DownloadStatus.Finished;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ArchiveOnCompressedBytesRead(Object sender, CompressedBytesReadEventArgs e)
|
|
||||||
{
|
|
||||||
if (_rarCurrentEntry == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_rarfileStatus[_rarCurrentEntry.Key] = e.CompressedBytesRead;
|
|
||||||
|
|
||||||
ActiveDownload.BytesDownloaded = _rarfileStatus.Sum(m => m.Value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using RdtClient.Data.Data;
|
using RdtClient.Data.Data;
|
||||||
using RdtClient.Data.Enums;
|
|
||||||
using RdtClient.Data.Models.Data;
|
using RdtClient.Data.Models.Data;
|
||||||
|
|
||||||
namespace RdtClient.Service.Services
|
namespace RdtClient.Service.Services
|
||||||
|
|
@ -11,8 +10,14 @@ namespace RdtClient.Service.Services
|
||||||
{
|
{
|
||||||
Task<IList<Download>> Get();
|
Task<IList<Download>> Get();
|
||||||
Task<IList<Download>> GetForTorrent(Guid torrentId);
|
Task<IList<Download>> GetForTorrent(Guid torrentId);
|
||||||
|
Task<Download> GetById(Guid downloadId);
|
||||||
Task<Download> Add(Guid torrentId, String link);
|
Task<Download> Add(Guid torrentId, String link);
|
||||||
Task UpdateStatus(Guid downloadId, DownloadStatus status);
|
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 UpdateError(Guid downloadId, String error);
|
||||||
Task DeleteForTorrent(Guid torrentId);
|
Task DeleteForTorrent(Guid torrentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,14 +40,44 @@ namespace RdtClient.Service.Services
|
||||||
return await _downloadData.GetForTorrent(torrentId);
|
return await _downloadData.GetForTorrent(torrentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<Download> GetById(Guid downloadId)
|
||||||
|
{
|
||||||
|
return await _downloadData.GetById(downloadId);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<Download> Add(Guid torrentId, String link)
|
public async Task<Download> Add(Guid torrentId, String link)
|
||||||
{
|
{
|
||||||
return await _downloadData.Add(torrentId, link);
|
return await _downloadData.Add(torrentId, link);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateStatus(Guid downloadId, DownloadStatus status)
|
public async Task UpdateDownloadStarted(Guid downloadId, DateTimeOffset? dateTime)
|
||||||
{
|
{
|
||||||
await _downloadData.UpdateStatus(downloadId, status);
|
await _downloadData.UpdateDownloadStarted(downloadId, dateTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateDownloadFinished(Guid downloadId, DateTimeOffset? dateTime)
|
||||||
|
{
|
||||||
|
await _downloadData.UpdateDownloadFinished(downloadId, dateTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateUnpackingQueued(Guid downloadId, DateTimeOffset? dateTime)
|
||||||
|
{
|
||||||
|
await _downloadData.UpdateUnpackingQueued(downloadId, dateTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateUnpackingStarted(Guid downloadId, DateTimeOffset? dateTime)
|
||||||
|
{
|
||||||
|
await _downloadData.UpdateUnpackingStarted(downloadId, dateTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateUnpackingFinished(Guid downloadId, DateTimeOffset? dateTime)
|
||||||
|
{
|
||||||
|
await _downloadData.UpdateUnpackingFinished(downloadId, dateTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateError(Guid downloadId, String error)
|
||||||
|
{
|
||||||
|
await _downloadData.UpdateError(downloadId, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeleteForTorrent(Guid torrentId)
|
public async Task DeleteForTorrent(Guid torrentId)
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@ namespace RdtClient.Service.Services
|
||||||
Task<IList<TorrentFileItem>> TorrentFileContents(String hash);
|
Task<IList<TorrentFileItem>> TorrentFileContents(String hash);
|
||||||
Task<TorrentProperties> TorrentProperties(String hash);
|
Task<TorrentProperties> TorrentProperties(String hash);
|
||||||
Task TorrentsDelete(String hash, Boolean deleteFiles);
|
Task TorrentsDelete(String hash, Boolean deleteFiles);
|
||||||
Task TorrentsAdd(String magnetLink, Boolean autoDownload, Boolean autoDelete);
|
Task TorrentsAdd(String magnetLink, Boolean autoDownload, Boolean autoUnpack, Boolean autoDelete);
|
||||||
Task TorrentsAddFile(Byte[] fileBytes, Boolean autoDownload, Boolean autoDelete);
|
Task TorrentsAddFile(Byte[] fileBytes, Boolean autoDownload, Boolean autoUnpack, Boolean autoDelete);
|
||||||
Task TorrentsSetCategory(String hash, String category);
|
Task TorrentsSetCategory(String hash, String category);
|
||||||
Task<IDictionary<String, TorrentCategory>> TorrentsCategories();
|
Task<IDictionary<String, TorrentCategory>> TorrentsCategories();
|
||||||
}
|
}
|
||||||
|
|
@ -275,14 +275,13 @@ namespace RdtClient.Service.Services
|
||||||
result.Uploaded = (Int64) (torrent.RdSize * (torrent.RdProgress / 100.0));
|
result.Uploaded = (Int64) (torrent.RdSize * (torrent.RdProgress / 100.0));
|
||||||
result.UploadedSession = (Int64) (torrent.RdSize * (torrent.RdProgress / 100.0));
|
result.UploadedSession = (Int64) (torrent.RdSize * (torrent.RdProgress / 100.0));
|
||||||
result.Upspeed = torrent.RdSpeed ?? 0;
|
result.Upspeed = torrent.RdSpeed ?? 0;
|
||||||
result.State = torrent.Status switch
|
result.State = torrent.RdStatus switch
|
||||||
{
|
{
|
||||||
TorrentStatus.RealDebrid => "downloading",
|
RealDebridStatus.Processing => "downloading",
|
||||||
TorrentStatus.WaitingForDownload => "downloading",
|
RealDebridStatus.WaitingForFileSelection => "downloading",
|
||||||
TorrentStatus.DownloadQueued => "downloading",
|
RealDebridStatus.Downloading => "downloading",
|
||||||
TorrentStatus.Downloading => "downloading",
|
RealDebridStatus.Finished => "pausedUP",
|
||||||
TorrentStatus.Finished => "pausedUP",
|
RealDebridStatus.Error => "error",
|
||||||
TorrentStatus.Error => "error",
|
|
||||||
_ => throw new ArgumentOutOfRangeException()
|
_ => throw new ArgumentOutOfRangeException()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -304,8 +303,11 @@ namespace RdtClient.Service.Services
|
||||||
|
|
||||||
foreach (var file in torrent.Files)
|
foreach (var file in torrent.Files)
|
||||||
{
|
{
|
||||||
var result = new TorrentFileItem();
|
var result = new TorrentFileItem
|
||||||
result.Name = file.Path;
|
{
|
||||||
|
Name = file.Path
|
||||||
|
};
|
||||||
|
|
||||||
results.Add(result);
|
results.Add(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -340,7 +342,7 @@ namespace RdtClient.Service.Services
|
||||||
Peers = 0,
|
Peers = 0,
|
||||||
PeersTotal = 2,
|
PeersTotal = 2,
|
||||||
PieceSize = torrent.Files.Count > 0 ? torrent.RdSize / torrent.Files.Count : 0,
|
PieceSize = torrent.Files.Count > 0 ? torrent.RdSize / torrent.Files.Count : 0,
|
||||||
PiecesHave = torrent.Downloads.Count(m => m.Status == DownloadStatus.Finished),
|
PiecesHave = torrent.Downloads.Count(m => m.Completed.HasValue),
|
||||||
PiecesNum = torrent.Files.Count,
|
PiecesNum = torrent.Files.Count,
|
||||||
Reannounce = 0,
|
Reannounce = 0,
|
||||||
SavePath = savePath,
|
SavePath = savePath,
|
||||||
|
|
@ -372,7 +374,7 @@ namespace RdtClient.Service.Services
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await _torrents.Delete(torrent.TorrentId);
|
await _torrents.Delete(torrent.TorrentId, true, true, true);
|
||||||
|
|
||||||
if (deleteFiles)
|
if (deleteFiles)
|
||||||
{
|
{
|
||||||
|
|
@ -387,14 +389,14 @@ namespace RdtClient.Service.Services
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task TorrentsAdd(String magnetLink, Boolean autoDownload, Boolean autoDelete)
|
public async Task TorrentsAdd(String magnetLink, Boolean autoDownload, Boolean autoUnpack, Boolean autoDelete)
|
||||||
{
|
{
|
||||||
await _torrents.UploadMagnet(magnetLink, autoDownload, autoDelete);
|
await _torrents.UploadMagnet(magnetLink, autoDownload, autoUnpack, autoDelete);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task TorrentsAddFile(Byte[] fileBytes, Boolean autoDownload, Boolean autoDelete)
|
public async Task TorrentsAddFile(Byte[] fileBytes, Boolean autoDownload, Boolean autoUnpack, Boolean autoDelete)
|
||||||
{
|
{
|
||||||
await _torrents.UploadFile(fileBytes, autoDownload, autoDelete);
|
await _torrents.UploadFile(fileBytes, autoDownload, autoUnpack, autoDelete);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task TorrentsSetCategory(String hash, String category)
|
public async Task TorrentsSetCategory(String hash, String category)
|
||||||
|
|
|
||||||
|
|
@ -1,157 +1,53 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using RdtClient.Data.Enums;
|
|
||||||
|
|
||||||
namespace RdtClient.Service.Services
|
namespace RdtClient.Service.Services
|
||||||
{
|
{
|
||||||
public class TaskRunner : BackgroundService
|
public class TaskRunner : BackgroundService
|
||||||
{
|
{
|
||||||
public static readonly ConcurrentDictionary<Guid, DownloadManager> ActiveDownloads = new ConcurrentDictionary<Guid, DownloadManager>();
|
|
||||||
|
|
||||||
private readonly ILogger<TaskRunner> _logger;
|
private readonly ILogger<TaskRunner> _logger;
|
||||||
private readonly IServiceProvider _services;
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
|
||||||
public TaskRunner(ILogger<TaskRunner> logger, IServiceProvider services)
|
public TaskRunner(ILogger<TaskRunner> logger, IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_services = services;
|
_serviceProvider = serviceProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
{
|
{
|
||||||
|
#if DEBUG
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(3), stoppingToken);
|
||||||
|
#else
|
||||||
await Task.Delay(TimeSpan.FromSeconds(15), stoppingToken);
|
await Task.Delay(TimeSpan.FromSeconds(15), stoppingToken);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
using var scope = _serviceProvider.CreateScope();
|
||||||
|
var torrentRunner = scope.ServiceProvider.GetRequiredService<ITorrentRunner>();
|
||||||
|
|
||||||
_logger.LogInformation("TaskRunner started.");
|
_logger.LogInformation("TaskRunner started.");
|
||||||
|
|
||||||
|
await torrentRunner.Initialize();
|
||||||
|
|
||||||
while (!stoppingToken.IsCancellationRequested)
|
while (!stoppingToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
await DoWork();
|
try
|
||||||
|
{
|
||||||
|
await torrentRunner.Tick();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Unexpected error occurred in TorrentDownloadManager.Tick");
|
||||||
|
}
|
||||||
|
|
||||||
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
|
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation("TaskRunner stopped.");
|
_logger.LogInformation("TaskRunner stopped.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DoWork()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var scope = _services.CreateScope();
|
|
||||||
|
|
||||||
var downloads = scope.ServiceProvider.GetRequiredService<IDownloads>();
|
|
||||||
var settings = scope.ServiceProvider.GetRequiredService<ISettings>();
|
|
||||||
var torrents = scope.ServiceProvider.GetRequiredService<ITorrents>();
|
|
||||||
|
|
||||||
var rdKey = await settings.GetString("RealDebridApiKey");
|
|
||||||
|
|
||||||
if (String.IsNullOrWhiteSpace(rdKey))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await torrents.Update();
|
|
||||||
|
|
||||||
await ProcessAutoDownloads(downloads, settings, torrents);
|
|
||||||
await ProcessDownloads(downloads, settings, torrents);
|
|
||||||
await ProcessStatus(downloads, settings, torrents);
|
|
||||||
}
|
|
||||||
catch(Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, ex.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ProcessAutoDownloads(IDownloads downloads, ISettings settings, ITorrents torrents)
|
|
||||||
{
|
|
||||||
var allTorrents = await torrents.Get();
|
|
||||||
|
|
||||||
allTorrents = allTorrents.Where(m => (m.Status == TorrentStatus.WaitingForDownload && m.AutoDownload && m.Downloads.Count == 0) || m.Status == TorrentStatus.DownloadQueued)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
foreach (var torrent in allTorrents)
|
|
||||||
{
|
|
||||||
await torrents.Download(torrent.TorrentId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ProcessDownloads(IDownloads downloads, ISettings settings, ITorrents torrents)
|
|
||||||
{
|
|
||||||
var allDownloads = await downloads.Get();
|
|
||||||
|
|
||||||
allDownloads = allDownloads.Where(m => m.Status != DownloadStatus.Finished)
|
|
||||||
.OrderByDescending(m => m.Status)
|
|
||||||
.ThenByDescending(m => m.Added)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var maxDownloads = await settings.GetNumber("DownloadLimit");
|
|
||||||
var destinationFolderPath = await settings.GetString("DownloadFolder");
|
|
||||||
|
|
||||||
foreach (var download in allDownloads)
|
|
||||||
{
|
|
||||||
if (ActiveDownloads.ContainsKey(download.DownloadId))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ActiveDownloads.Count >= maxDownloads)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prevent circular references
|
|
||||||
download.Torrent.Downloads = null;
|
|
||||||
|
|
||||||
await torrents.UpdateStatus(download.TorrentId, TorrentStatus.Downloading);
|
|
||||||
|
|
||||||
await Task.Factory.StartNew(async delegate
|
|
||||||
{
|
|
||||||
var downloadManager = new DownloadManager();
|
|
||||||
|
|
||||||
if (ActiveDownloads.TryAdd(download.DownloadId, downloadManager))
|
|
||||||
{
|
|
||||||
downloadManager.Download = download;
|
|
||||||
await downloadManager.Start(destinationFolderPath, download.Torrent.RdName, download.Status);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ProcessStatus(IDownloads downloads, ISettings settings, ITorrents torrents)
|
|
||||||
{
|
|
||||||
foreach (var (downloadId, download) in ActiveDownloads)
|
|
||||||
{
|
|
||||||
if (download.NewStatus.HasValue)
|
|
||||||
{
|
|
||||||
download.Download.Status = download.NewStatus.Value;
|
|
||||||
download.NewStatus = null;
|
|
||||||
|
|
||||||
await downloads.UpdateStatus(downloadId, download.Download.Status);
|
|
||||||
|
|
||||||
if (download.Download.Status == DownloadStatus.Finished)
|
|
||||||
{
|
|
||||||
ActiveDownloads.TryRemove(downloadId, out _);
|
|
||||||
|
|
||||||
// Check if all downloads are completed and update the torrent
|
|
||||||
var allDownloads = await downloads.GetForTorrent(download.Download.TorrentId);
|
|
||||||
|
|
||||||
if (allDownloads.All(m => m.Status == DownloadStatus.Finished))
|
|
||||||
{
|
|
||||||
await torrents.UpdateStatus(download.Download.TorrentId, TorrentStatus.Finished);
|
|
||||||
|
|
||||||
if (download.Download.Torrent.AutoDelete)
|
|
||||||
{
|
|
||||||
await torrents.Delete(download.Download.TorrentId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
17
server/RdtClient.Service/Services/TorrentDownloadManager.cs
Normal file
17
server/RdtClient.Service/Services/TorrentDownloadManager.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace RdtClient.Service.Services
|
||||||
|
{
|
||||||
|
public interface ITorrentDownloadManager
|
||||||
|
{
|
||||||
|
Task Tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TorrentDownloadManager : ITorrentDownloadManager
|
||||||
|
{
|
||||||
|
public async Task Tick()
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
229
server/RdtClient.Service/Services/TorrentRunner.cs
Normal file
229
server/RdtClient.Service/Services/TorrentRunner.cs
Normal file
|
|
@ -0,0 +1,229 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using RdtClient.Data.Enums;
|
||||||
|
|
||||||
|
namespace RdtClient.Service.Services
|
||||||
|
{
|
||||||
|
public interface ITorrentRunner
|
||||||
|
{
|
||||||
|
Task Initialize();
|
||||||
|
Task Tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TorrentRunner : ITorrentRunner
|
||||||
|
{
|
||||||
|
private static DateTime _nextUpdate = DateTime.UtcNow;
|
||||||
|
|
||||||
|
public static readonly ConcurrentDictionary<Guid, DownloadClient> ActiveDownloadClients = new ConcurrentDictionary<Guid, DownloadClient>();
|
||||||
|
public static readonly ConcurrentDictionary<Guid, UnpackClient> ActiveUnpackClients = new ConcurrentDictionary<Guid, UnpackClient>();
|
||||||
|
|
||||||
|
private readonly ISettings _settings;
|
||||||
|
private readonly ITorrents _torrents;
|
||||||
|
private readonly IDownloads _downloads;
|
||||||
|
|
||||||
|
public TorrentRunner(ISettings settings, ITorrents torrents, IDownloads downloads)
|
||||||
|
{
|
||||||
|
_settings = settings;
|
||||||
|
_torrents = torrents;
|
||||||
|
_downloads = downloads;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Initialize()
|
||||||
|
{
|
||||||
|
// When starting up reset any pending downloads or unpackings so that they are restarted.
|
||||||
|
var torrents = await _torrents.Get();
|
||||||
|
|
||||||
|
var downloads = torrents.SelectMany(m => m.Downloads)
|
||||||
|
.Where(m => m.DownloadQueued != null && m.DownloadStarted != null && m.DownloadFinished == null)
|
||||||
|
.OrderBy(m => m.DownloadQueued);
|
||||||
|
|
||||||
|
foreach (var download in downloads)
|
||||||
|
{
|
||||||
|
await _downloads.UpdateDownloadStarted(download.DownloadId, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
var unpacks = torrents.SelectMany(m => m.Downloads)
|
||||||
|
.Where(m => m.UnpackingQueued != null && m.UnpackingStarted != null && m.UnpackingFinished == null)
|
||||||
|
.OrderBy(m => m.DownloadQueued);
|
||||||
|
|
||||||
|
foreach (var download in unpacks)
|
||||||
|
{
|
||||||
|
await _downloads.UpdateUnpackingStarted(download.DownloadId, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Tick()
|
||||||
|
{
|
||||||
|
var settingApiKey = await _settings.GetString("RealDebridApiKey");
|
||||||
|
|
||||||
|
if (String.IsNullOrWhiteSpace(settingApiKey))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if any torrents are finished downloading to the host, remove them from the active download list.
|
||||||
|
var completedActiveDownloads = ActiveDownloadClients.Where(m => m.Value.Finished).ToList();
|
||||||
|
|
||||||
|
if (completedActiveDownloads.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var (downloadId, downloadClient) in completedActiveDownloads)
|
||||||
|
{
|
||||||
|
if (downloadClient.Error != null)
|
||||||
|
{
|
||||||
|
await _downloads.UpdateError(downloadId, downloadClient.Error);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await _downloads.UpdateDownloadFinished(downloadId, DateTimeOffset.UtcNow);
|
||||||
|
}
|
||||||
|
|
||||||
|
ActiveDownloadClients.TryRemove(downloadId, out _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if any torrents are finished unpacking, remove them from the active unpack list.
|
||||||
|
var completedUnpacks = ActiveUnpackClients.Where(m => m.Value.Finished).ToList();
|
||||||
|
|
||||||
|
if (completedUnpacks.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var (downloadId, unpackClient) in completedUnpacks)
|
||||||
|
{
|
||||||
|
if (unpackClient.Error != null)
|
||||||
|
{
|
||||||
|
await _downloads.UpdateError(downloadId, unpackClient.Error);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await _downloads.UpdateUnpackingFinished(downloadId, DateTimeOffset.UtcNow);
|
||||||
|
}
|
||||||
|
|
||||||
|
ActiveUnpackClients.TryRemove(downloadId, out _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only poll RealDebrid every 30 seconds
|
||||||
|
|
||||||
|
if (_nextUpdate < DateTime.UtcNow)
|
||||||
|
{
|
||||||
|
_nextUpdate = DateTime.UtcNow.AddSeconds(5);
|
||||||
|
|
||||||
|
await _torrents.Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
var torrents = await _torrents.Get();
|
||||||
|
|
||||||
|
torrents = torrents.Where(m => m.Completed == null).ToList();
|
||||||
|
|
||||||
|
// Check if there are any downloads that are queued and can be started.
|
||||||
|
var queuedDownloads = torrents.SelectMany(m => m.Downloads)
|
||||||
|
.Where(m => m.DownloadQueued != null && m.DownloadStarted == null)
|
||||||
|
.OrderBy(m => m.DownloadQueued);
|
||||||
|
|
||||||
|
foreach (var download in queuedDownloads)
|
||||||
|
{
|
||||||
|
await _torrents.Download(download.DownloadId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there are any unpacks that are queued and can be started.
|
||||||
|
var queuedUnpacks = torrents.SelectMany(m => m.Downloads)
|
||||||
|
.Where(m => m.UnpackingQueued != null && m.UnpackingStarted == null)
|
||||||
|
.OrderBy(m => m.DownloadQueued);
|
||||||
|
|
||||||
|
foreach (var download in queuedUnpacks)
|
||||||
|
{
|
||||||
|
await _torrents.Unpack(download.DownloadId);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var torrent in torrents)
|
||||||
|
{
|
||||||
|
// If torrent is erroring out on the RealDebrid side, skip processing this torrent.
|
||||||
|
if (torrent.RdStatus == RealDebridStatus.Error)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// RealDebrid is waiting for file selection, select which files to download.
|
||||||
|
if (torrent.AutoDownload && torrent.RdStatus == RealDebridStatus.WaitingForFileSelection)
|
||||||
|
{
|
||||||
|
var fileIds = torrent.Files
|
||||||
|
.Select(m => m.Id.ToString())
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
await _torrents.SelectFiles(torrent.RdId, fileIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the torrent doesn't have any files at this point, don't process it further.
|
||||||
|
if (torrent.Files.Count == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// RealDebrid finished downloading the torrent, process the file to host.
|
||||||
|
if (torrent.AutoDownload && torrent.RdStatus == RealDebridStatus.Finished)
|
||||||
|
{
|
||||||
|
// If the torrent doesn't have any Downloads, unrestrict the links and add them to the database.
|
||||||
|
if (torrent.Downloads.Count == 0)
|
||||||
|
{
|
||||||
|
await _torrents.Unrestrict(torrent.TorrentId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the torrent has any files that need starting to be downloaded, download them.
|
||||||
|
var downloadsPending = torrent.Downloads
|
||||||
|
.Where(m => m.DownloadStarted == null &&
|
||||||
|
m.DownloadFinished == null)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (downloadsPending.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var download in downloadsPending)
|
||||||
|
{
|
||||||
|
await _torrents.Download(download.DownloadId);
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (torrent.AutoUnpack && torrent.RdStatus == RealDebridStatus.Finished)
|
||||||
|
{
|
||||||
|
// If all files are finished downloading, move to the unpacking step.
|
||||||
|
var unpackingPending = torrent.Downloads
|
||||||
|
.Where(m => m.DownloadFinished != null &&
|
||||||
|
m.UnpackingStarted == null &&
|
||||||
|
m.UnpackingFinished == null)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (unpackingPending.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var download in unpackingPending)
|
||||||
|
{
|
||||||
|
await _torrents.Unpack(download.DownloadId);
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if torrent is complete
|
||||||
|
if (torrent.Downloads.Count > 0)
|
||||||
|
{
|
||||||
|
var allComplete = torrent.Downloads.All(m => m.UnpackingFinished != null);
|
||||||
|
|
||||||
|
if (allComplete)
|
||||||
|
{
|
||||||
|
await _torrents.UpdateComplete(torrent.TorrentId, DateTimeOffset.UtcNow);
|
||||||
|
|
||||||
|
// Remove the torrent from RealDebrid
|
||||||
|
if (torrent.AutoDelete)
|
||||||
|
{
|
||||||
|
await _torrents.Delete(torrent.TorrentId, true, true, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -16,24 +17,28 @@ namespace RdtClient.Service.Services
|
||||||
public interface ITorrents
|
public interface ITorrents
|
||||||
{
|
{
|
||||||
Task<IList<Torrent>> Get();
|
Task<IList<Torrent>> Get();
|
||||||
Task<Torrent> GetById(Guid id);
|
Task<Torrent> GetById(Guid torrentId);
|
||||||
Task<Torrent> GetByHash(String hash);
|
Task<Torrent> GetByHash(String hash);
|
||||||
Task<IList<Torrent>> Update();
|
|
||||||
Task UpdateStatus(Guid torrentId, TorrentStatus status);
|
|
||||||
Task UpdateCategory(String hash, String category);
|
Task UpdateCategory(String hash, String category);
|
||||||
Task UploadMagnet(String magnetLink, Boolean autoDownload, Boolean autoDelete);
|
Task UploadMagnet(String magnetLink, Boolean autoDownload, Boolean autoUnpack, Boolean autoDelete);
|
||||||
Task UploadFile(Byte[] bytes, Boolean autoDownload, Boolean autoDelete);
|
Task UploadFile(Byte[] bytes, Boolean autoDownload, Boolean autoUnpack, Boolean autoDelete);
|
||||||
Task Delete(Guid id);
|
Task SelectFiles(String torrentId, IList<String> fileIds);
|
||||||
Task Download(Guid id);
|
Task Delete(Guid torrentId, Boolean deleteData, Boolean deleteRdTorrent, Boolean deleteLocalFiles);
|
||||||
|
Task Unrestrict(Guid torrentId);
|
||||||
|
Task Download(Guid downloadId);
|
||||||
|
Task Unpack(Guid downloadId);
|
||||||
void Reset();
|
void Reset();
|
||||||
Task<Profile> GetProfile();
|
Task<Profile> GetProfile();
|
||||||
|
Task UpdateComplete(Guid torrentId, DateTimeOffset datetime);
|
||||||
|
Task Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Torrents : ITorrents
|
public class Torrents : ITorrents
|
||||||
{
|
{
|
||||||
private static RdNetClient _rdtClient;
|
private static RdNetClient _rdtNetClient;
|
||||||
|
|
||||||
private static readonly SemaphoreSlim SemaphoreSlim = new SemaphoreSlim(1, 1);
|
private static readonly SemaphoreSlim SemaphoreSlim = new SemaphoreSlim(1, 1);
|
||||||
|
|
||||||
private readonly IDownloads _downloads;
|
private readonly IDownloads _downloads;
|
||||||
private readonly ISettings _settings;
|
private readonly ISettings _settings;
|
||||||
private readonly ITorrentData _torrentData;
|
private readonly ITorrentData _torrentData;
|
||||||
|
|
@ -45,25 +50,22 @@ namespace RdtClient.Service.Services
|
||||||
_downloads = downloads;
|
_downloads = downloads;
|
||||||
}
|
}
|
||||||
|
|
||||||
private RdNetClient RdNetClient
|
private RdNetClient GetRdNetClient()
|
||||||
{
|
{
|
||||||
get
|
if (_rdtNetClient == null)
|
||||||
{
|
{
|
||||||
if (_rdtClient == null)
|
var apiKey = _settings.GetString("RealDebridApiKey")
|
||||||
|
.Result;
|
||||||
|
|
||||||
|
if (String.IsNullOrWhiteSpace(apiKey))
|
||||||
{
|
{
|
||||||
var apiKey = _settings.GetString("RealDebridApiKey")
|
throw new Exception("RealDebrid API Key not set in the settings");
|
||||||
.Result;
|
|
||||||
|
|
||||||
if (String.IsNullOrWhiteSpace(apiKey))
|
|
||||||
{
|
|
||||||
throw new Exception("RealDebrid API Key not set in the settings");
|
|
||||||
}
|
|
||||||
|
|
||||||
_rdtClient = new RdNetClient("X245A4XAIBGVM", null, null, null, apiKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return _rdtClient;
|
_rdtNetClient = new RdNetClient("X245A4XAIBGVM", null, null, null, apiKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return _rdtNetClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IList<Torrent>> Get()
|
public async Task<IList<Torrent>> Get()
|
||||||
|
|
@ -74,16 +76,17 @@ namespace RdtClient.Service.Services
|
||||||
{
|
{
|
||||||
foreach (var download in torrent.Downloads)
|
foreach (var download in torrent.Downloads)
|
||||||
{
|
{
|
||||||
if (TaskRunner.ActiveDownloads.TryGetValue(download.DownloadId, out var activeDownload))
|
if (TorrentRunner.ActiveDownloadClients.TryGetValue(download.DownloadId, out var downloadClient))
|
||||||
{
|
{
|
||||||
download.Speed = activeDownload.Speed;
|
download.Speed = downloadClient.Speed;
|
||||||
download.BytesSize = activeDownload.BytesSize;
|
download.BytesTotal = downloadClient.BytesTotal;
|
||||||
download.BytesDownloaded = activeDownload.BytesDownloaded;
|
download.BytesDone = downloadClient.BytesDone;
|
||||||
|
}
|
||||||
if (activeDownload.NewStatus.HasValue)
|
|
||||||
{
|
if (TorrentRunner.ActiveUnpackClients.TryGetValue(download.DownloadId, out var unpackClient))
|
||||||
download.Status = activeDownload.NewStatus.Value;
|
{
|
||||||
}
|
download.BytesTotal = unpackClient.BytesTotal;
|
||||||
|
download.BytesDone = unpackClient.BytesDone;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -91,13 +94,13 @@ namespace RdtClient.Service.Services
|
||||||
return torrents;
|
return torrents;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Torrent> GetById(Guid id)
|
public async Task<Torrent> GetById(Guid torrentId)
|
||||||
{
|
{
|
||||||
var torrent = await _torrentData.GetById(id);
|
var torrent = await _torrentData.GetById(torrentId);
|
||||||
|
|
||||||
if (torrent != null)
|
if (torrent != null)
|
||||||
{
|
{
|
||||||
var rdTorrent = await RdNetClient.GetTorrentInfoAsync(torrent.RdId);
|
var rdTorrent = await GetRdNetClient().GetTorrentInfoAsync(torrent.RdId);
|
||||||
|
|
||||||
await Update(torrent, rdTorrent);
|
await Update(torrent, rdTorrent);
|
||||||
}
|
}
|
||||||
|
|
@ -111,7 +114,7 @@ namespace RdtClient.Service.Services
|
||||||
|
|
||||||
if (torrent != null)
|
if (torrent != null)
|
||||||
{
|
{
|
||||||
var rdTorrent = await RdNetClient.GetTorrentInfoAsync(torrent.RdId);
|
var rdTorrent = await GetRdNetClient().GetTorrentInfoAsync(torrent.RdId);
|
||||||
|
|
||||||
await Update(torrent, rdTorrent);
|
await Update(torrent, rdTorrent);
|
||||||
}
|
}
|
||||||
|
|
@ -119,20 +122,224 @@ namespace RdtClient.Service.Services
|
||||||
return torrent;
|
return torrent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IList<Torrent>> Update()
|
public async Task UpdateCategory(String hash, String category)
|
||||||
|
{
|
||||||
|
var torrent = await _torrentData.GetByHash(hash);
|
||||||
|
|
||||||
|
if (torrent == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _torrentData.UpdateCategory(torrent.TorrentId, category);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UploadMagnet(String magnetLink, Boolean autoDownload, Boolean autoUnpack, Boolean autoDelete)
|
||||||
|
{
|
||||||
|
var magnet = MagnetLink.Parse(magnetLink);
|
||||||
|
|
||||||
|
var rdTorrent = await GetRdNetClient().AddTorrentMagnetAsync(magnetLink);
|
||||||
|
|
||||||
|
await Add(rdTorrent.Id, magnet.InfoHash.ToHex(), autoDownload, autoUnpack, autoDelete);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UploadFile(Byte[] bytes, Boolean autoDownload, Boolean autoUnpack, Boolean autoDelete)
|
||||||
|
{
|
||||||
|
var torrent = await MonoTorrent.Torrent.LoadAsync(bytes);
|
||||||
|
|
||||||
|
var rdTorrent = await GetRdNetClient().AddTorrentFileAsync(bytes);
|
||||||
|
|
||||||
|
await Add(rdTorrent.Id, torrent.InfoHash.ToHex(), autoDownload, autoUnpack, autoDelete);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SelectFiles(String torrentId, IList<String> fileIds)
|
||||||
|
{
|
||||||
|
await GetRdNetClient().SelectTorrentFilesAsync(torrentId, fileIds.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Delete(Guid torrentId, Boolean deleteData, Boolean deleteRdTorrent, Boolean deleteLocalFiles)
|
||||||
|
{
|
||||||
|
var torrent = await GetById(torrentId);
|
||||||
|
|
||||||
|
if (torrent != null)
|
||||||
|
{
|
||||||
|
if (deleteData)
|
||||||
|
{
|
||||||
|
await _downloads.DeleteForTorrent(torrent.TorrentId);
|
||||||
|
await _torrentData.Delete(torrentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deleteRdTorrent)
|
||||||
|
{
|
||||||
|
await GetRdNetClient().DeleteTorrentAsync(torrent.RdId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deleteLocalFiles)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Unrestrict(Guid torrentId)
|
||||||
|
{
|
||||||
|
var torrent = await _torrentData.GetById(torrentId);
|
||||||
|
|
||||||
|
var rdTorrent = await GetRdNetClient().GetTorrentInfoAsync(torrent.RdId);
|
||||||
|
|
||||||
|
foreach (var link in rdTorrent.Links)
|
||||||
|
{
|
||||||
|
var unrestrictedLink = await GetRdNetClient().UnrestrictLinkAsync(link);
|
||||||
|
|
||||||
|
if (torrent.Downloads.Any(m => m.Link == unrestrictedLink.Download))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _downloads.Add(torrent.TorrentId, unrestrictedLink.Download);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Download(Guid downloadId)
|
||||||
|
{
|
||||||
|
var settingDownloadLimit = await _settings.GetNumber("DownloadLimit");
|
||||||
|
var settingDownloadFolder = await _settings.GetString("DownloadFolder");
|
||||||
|
|
||||||
|
var download = await _downloads.GetById(downloadId);
|
||||||
|
|
||||||
|
// Check if we have reached the download limit, if so queue the download, but don't start it.
|
||||||
|
if (TorrentRunner.ActiveDownloadClients.Count >= settingDownloadLimit)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
download.DownloadStarted = DateTimeOffset.UtcNow;
|
||||||
|
await _downloads.UpdateDownloadStarted(download.DownloadId, download.DownloadStarted);
|
||||||
|
|
||||||
|
// Start the download process
|
||||||
|
var downloadClient = new DownloadClient(download, settingDownloadFolder);
|
||||||
|
|
||||||
|
if (TorrentRunner.ActiveDownloadClients.TryAdd(downloadId, downloadClient))
|
||||||
|
{
|
||||||
|
await downloadClient.Start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Unpack(Guid downloadId)
|
||||||
|
{
|
||||||
|
//var settingUnpackLimit = await _settings.GetNumber("UnpackLimit");
|
||||||
|
var settingUnpackLimit = 1;
|
||||||
|
var settingDownloadFolder = await _settings.GetString("DownloadFolder");
|
||||||
|
|
||||||
|
var download = await _downloads.GetById(downloadId);
|
||||||
|
|
||||||
|
// Check if the file is even unpackable.
|
||||||
|
var uri = new Uri(download.Link);
|
||||||
|
var fileName = uri.Segments.Last();
|
||||||
|
var extension = Path.GetExtension(fileName);
|
||||||
|
|
||||||
|
if (extension != ".rar")
|
||||||
|
{
|
||||||
|
await _downloads.UpdateUnpackingQueued(downloadId, DateTimeOffset.UtcNow);
|
||||||
|
await _downloads.UpdateUnpackingStarted(downloadId, DateTimeOffset.UtcNow);
|
||||||
|
await _downloads.UpdateUnpackingFinished(downloadId, DateTimeOffset.UtcNow);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This property can have a value when it is a retry.
|
||||||
|
if (download.UnpackingQueued == null)
|
||||||
|
{
|
||||||
|
download.UnpackingQueued = DateTimeOffset.UtcNow;
|
||||||
|
await _downloads.UpdateUnpackingQueued(download.DownloadId, download.UnpackingQueued);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we have reached the download limit, if so queue the download, but don't start it.
|
||||||
|
if (TorrentRunner.ActiveUnpackClients.Count >= settingUnpackLimit)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
download.UnpackingStarted = DateTimeOffset.UtcNow;
|
||||||
|
await _downloads.UpdateUnpackingStarted(download.DownloadId, download.UnpackingStarted);
|
||||||
|
|
||||||
|
// Start the unpacking process
|
||||||
|
var unpackClient = new UnpackClient(download, settingDownloadFolder);
|
||||||
|
|
||||||
|
if (TorrentRunner.ActiveUnpackClients.TryAdd(downloadId, unpackClient))
|
||||||
|
{
|
||||||
|
await unpackClient.Start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
_rdtNetClient = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Profile> GetProfile()
|
||||||
|
{
|
||||||
|
if (_rdtNetClient == null)
|
||||||
|
{
|
||||||
|
return new Profile();
|
||||||
|
}
|
||||||
|
|
||||||
|
var user = await GetRdNetClient().GetUserAsync();
|
||||||
|
|
||||||
|
var profile = new Profile
|
||||||
|
{
|
||||||
|
UserName = user.Username,
|
||||||
|
Expiration = user.Expiration
|
||||||
|
};
|
||||||
|
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Add(String rdTorrentId, String infoHash, Boolean autoDownload, Boolean autoUnpack, Boolean autoDelete)
|
||||||
|
{
|
||||||
|
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, autoDownload, autoUnpack, autoDelete);
|
||||||
|
|
||||||
|
var rdTorrent = await GetRdNetClient().GetTorrentInfoAsync(rdTorrentId);
|
||||||
|
|
||||||
|
await Update(newTorrent, rdTorrent);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
SemaphoreSlim.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Update()
|
||||||
{
|
{
|
||||||
var w = await SemaphoreSlim.WaitAsync(1);
|
var w = await SemaphoreSlim.WaitAsync(1);
|
||||||
|
|
||||||
if (!w)
|
if (!w)
|
||||||
{
|
{
|
||||||
return await _torrentData.Get();
|
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var torrents = await _torrentData.Get();
|
var torrents = await Get();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var rdTorrents = await RdNetClient.GetTorrentsAsync(0, 100);
|
var rdTorrents = await GetRdNetClient().GetTorrentsAsync(0, 100);
|
||||||
|
|
||||||
foreach (var rdTorrent in rdTorrents)
|
foreach (var rdTorrent in rdTorrents)
|
||||||
{
|
{
|
||||||
|
|
@ -140,13 +347,12 @@ namespace RdtClient.Service.Services
|
||||||
|
|
||||||
if (torrent == null)
|
if (torrent == null)
|
||||||
{
|
{
|
||||||
var newTorrent = await _torrentData.Add(rdTorrent.Id, rdTorrent.Hash, false, false);
|
continue;
|
||||||
await GetById(newTorrent.TorrentId);
|
|
||||||
}
|
}
|
||||||
else if (rdTorrent.Files == null)
|
if (rdTorrent.Files == null)
|
||||||
{
|
{
|
||||||
var rdTorrent2 = await RdNetClient.GetTorrentInfoAsync(rdTorrent.Id);
|
var rdTorrentInfo = await GetRdNetClient().GetTorrentInfoAsync(rdTorrent.Id);
|
||||||
await Update(torrent, rdTorrent2);
|
await Update(torrent, rdTorrentInfo);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -164,8 +370,6 @@ namespace RdtClient.Service.Services
|
||||||
await _torrentData.Delete(torrent.TorrentId);
|
await _torrentData.Delete(torrent.TorrentId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return torrents;
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
@ -173,128 +377,9 @@ namespace RdtClient.Service.Services
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateStatus(Guid torrentId, TorrentStatus status)
|
public async Task UpdateComplete(Guid torrentId, DateTimeOffset datetime)
|
||||||
{
|
{
|
||||||
await _torrentData.UpdateStatus(torrentId, status);
|
await _torrentData.UpdateComplete(torrentId, datetime);
|
||||||
}
|
|
||||||
|
|
||||||
public async Task UpdateCategory(String hash, String category)
|
|
||||||
{
|
|
||||||
var torrent = await _torrentData.GetByHash(hash);
|
|
||||||
|
|
||||||
if (torrent == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await _torrentData.UpdateCategory(torrent.TorrentId, category);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task UploadMagnet(String magnetLink, Boolean autoDownload, Boolean autoDelete)
|
|
||||||
{
|
|
||||||
var magnet = MagnetLink.Parse(magnetLink);
|
|
||||||
|
|
||||||
var rdTorrent = await RdNetClient.AddTorrentMagnetAsync(magnetLink);
|
|
||||||
|
|
||||||
await Add(rdTorrent.Id, magnet.InfoHash.ToHex(), autoDownload, autoDelete);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task UploadFile(Byte[] bytes, Boolean autoDownload, Boolean autoDelete)
|
|
||||||
{
|
|
||||||
var torrent = await MonoTorrent.Torrent.LoadAsync(bytes);
|
|
||||||
|
|
||||||
var rdTorrent = await RdNetClient.AddTorrentFileAsync(bytes);
|
|
||||||
|
|
||||||
await Add(rdTorrent.Id, torrent.InfoHash.ToHex(), autoDownload, autoDelete);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Delete(Guid id)
|
|
||||||
{
|
|
||||||
var torrent = await GetById(id);
|
|
||||||
|
|
||||||
if (torrent != null)
|
|
||||||
{
|
|
||||||
await _downloads.DeleteForTorrent(torrent.TorrentId);
|
|
||||||
await _torrentData.Delete(id);
|
|
||||||
await RdNetClient.DeleteTorrentAsync(torrent.RdId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Download(Guid id)
|
|
||||||
{
|
|
||||||
var torrent = await _torrentData.GetById(id);
|
|
||||||
|
|
||||||
await _downloads.DeleteForTorrent(id);
|
|
||||||
|
|
||||||
await _torrentData.UpdateStatus(id, TorrentStatus.DownloadQueued);
|
|
||||||
|
|
||||||
var rdTorrent = await RdNetClient.GetTorrentInfoAsync(torrent.RdId);
|
|
||||||
|
|
||||||
var existingDownloads = await _downloads.GetForTorrent(id);
|
|
||||||
|
|
||||||
foreach (var link in rdTorrent.Links)
|
|
||||||
{
|
|
||||||
var unrestrictedLink = await RdNetClient.UnrestrictLinkAsync(link);
|
|
||||||
|
|
||||||
if (existingDownloads.Any(m => m.Link == unrestrictedLink.Download))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
await _downloads.Add(torrent.TorrentId, unrestrictedLink.Download);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Reset()
|
|
||||||
{
|
|
||||||
_rdtClient = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Profile> GetProfile()
|
|
||||||
{
|
|
||||||
if (RdNetClient == null)
|
|
||||||
{
|
|
||||||
return new Profile();
|
|
||||||
}
|
|
||||||
|
|
||||||
var user = await RdNetClient.GetUserAsync();
|
|
||||||
|
|
||||||
var profile = new Profile
|
|
||||||
{
|
|
||||||
UserName = user.Username,
|
|
||||||
Expiration = user.Expiration
|
|
||||||
};
|
|
||||||
|
|
||||||
return profile;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task Add(String rdTorrentId, String infoHash, Boolean autoDownload, Boolean autoDelete)
|
|
||||||
{
|
|
||||||
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, autoDownload, autoDelete);
|
|
||||||
|
|
||||||
var rdTorrent = await RdNetClient.GetTorrentInfoAsync(rdTorrentId);
|
|
||||||
|
|
||||||
await Update(newTorrent, rdTorrent);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
SemaphoreSlim.Release();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Update(Torrent torrent, RDNET.Torrent rdTorrent)
|
private async Task Update(Torrent torrent, RDNET.Torrent rdTorrent)
|
||||||
|
|
@ -325,47 +410,29 @@ namespace RdtClient.Service.Services
|
||||||
torrent.RdHost = rdTorrent.Host;
|
torrent.RdHost = rdTorrent.Host;
|
||||||
torrent.RdSplit = rdTorrent.Split;
|
torrent.RdSplit = rdTorrent.Split;
|
||||||
torrent.RdProgress = rdTorrent.Progress;
|
torrent.RdProgress = rdTorrent.Progress;
|
||||||
torrent.RdStatus = rdTorrent.Status;
|
|
||||||
torrent.RdAdded = rdTorrent.Added;
|
torrent.RdAdded = rdTorrent.Added;
|
||||||
torrent.RdEnded = rdTorrent.Ended;
|
torrent.RdEnded = rdTorrent.Ended;
|
||||||
torrent.RdSpeed = rdTorrent.Speed;
|
torrent.RdSpeed = rdTorrent.Speed;
|
||||||
torrent.RdSeeders = rdTorrent.Seeders;
|
torrent.RdSeeders = rdTorrent.Seeders;
|
||||||
|
torrent.RdStatusRaw = rdTorrent.Status;
|
||||||
|
|
||||||
|
torrent.RdStatus = rdTorrent.Status switch
|
||||||
|
{
|
||||||
|
"magnet_error" => RealDebridStatus.Error,
|
||||||
|
"magnet_conversion" => RealDebridStatus.Processing,
|
||||||
|
"waiting_files_selection" => RealDebridStatus.WaitingForFileSelection,
|
||||||
|
"queued" => RealDebridStatus.Downloading,
|
||||||
|
"downloading" => RealDebridStatus.Downloading,
|
||||||
|
"downloaded" => RealDebridStatus.Finished,
|
||||||
|
"error" => RealDebridStatus.Error,
|
||||||
|
"virus" => RealDebridStatus.Error,
|
||||||
|
"compressing" => RealDebridStatus.Downloading,
|
||||||
|
"uploading" => RealDebridStatus.Finished,
|
||||||
|
"dead" => RealDebridStatus.Error,
|
||||||
|
_ => RealDebridStatus.Error
|
||||||
|
};
|
||||||
|
|
||||||
await _torrentData.UpdateRdData(torrent);
|
await _torrentData.UpdateRdData(torrent);
|
||||||
|
|
||||||
if (torrent.Status == TorrentStatus.RealDebrid)
|
|
||||||
{
|
|
||||||
if (torrent.Status == TorrentStatus.RealDebrid && torrent.RdProgress == 100)
|
|
||||||
{
|
|
||||||
await _torrentData.UpdateStatus(torrent.TorrentId, TorrentStatus.WaitingForDownload);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Current status of the torrent: magnet_error, magnet_conversion, waiting_files_selection, queued, downloading, downloaded, error, virus, compressing, uploading, dead
|
|
||||||
torrent.Status = rdTorrent.Status switch
|
|
||||||
{
|
|
||||||
"magnet_error" => TorrentStatus.Error,
|
|
||||||
"error" => TorrentStatus.Error,
|
|
||||||
"virus" => TorrentStatus.Error,
|
|
||||||
"dead" => TorrentStatus.Error,
|
|
||||||
_ => TorrentStatus.RealDebrid
|
|
||||||
};
|
|
||||||
|
|
||||||
await _torrentData.UpdateStatus(torrent.TorrentId, torrent.Status);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rdTorrent.Files != null && rdTorrent.Files.Count > 0)
|
|
||||||
{
|
|
||||||
if (!rdTorrent.Files.Any(m => m.Selected))
|
|
||||||
{
|
|
||||||
var fileIds = rdTorrent.Files
|
|
||||||
.Select(m => m.Id.ToString())
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
await RdNetClient.SelectTorrentFilesAsync(rdTorrent.Id, fileIds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
152
server/RdtClient.Service/Services/UnpackClient.cs
Normal file
152
server/RdtClient.Service/Services/UnpackClient.cs
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using RdtClient.Data.Models.Data;
|
||||||
|
using SharpCompress.Archives;
|
||||||
|
using SharpCompress.Archives.Rar;
|
||||||
|
using SharpCompress.Common;
|
||||||
|
|
||||||
|
namespace RdtClient.Service.Services
|
||||||
|
{
|
||||||
|
public class UnpackClient
|
||||||
|
{
|
||||||
|
public Boolean Finished { get; private set; }
|
||||||
|
|
||||||
|
public String Error { get; private set; }
|
||||||
|
|
||||||
|
public Int64 BytesTotal { get; private set; }
|
||||||
|
public Int64 BytesDone { get; private set; }
|
||||||
|
|
||||||
|
private readonly Download _download;
|
||||||
|
private readonly String _destinationPath;
|
||||||
|
private readonly Torrent _torrent;
|
||||||
|
|
||||||
|
private RarArchiveEntry _rarCurrentEntry;
|
||||||
|
private Dictionary<String, Int64> _rarfileStatus;
|
||||||
|
|
||||||
|
public UnpackClient(Download download, String destinationPath)
|
||||||
|
{
|
||||||
|
_download = download;
|
||||||
|
_destinationPath = destinationPath;
|
||||||
|
_torrent = download.Torrent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Start()
|
||||||
|
{
|
||||||
|
BytesDone = 0;
|
||||||
|
BytesTotal = 0;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
|
||||||
|
var fileName = uri.Segments.Last();
|
||||||
|
var filePath = Path.Combine(torrentPath, fileName);
|
||||||
|
|
||||||
|
if (!File.Exists(filePath))
|
||||||
|
{
|
||||||
|
throw new Exception($"File {filePath} could not be extracted because it is missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Factory.StartNew(async delegate
|
||||||
|
{
|
||||||
|
await Unpack(filePath);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Error = $"An unexpected error occurred preparing download {_download.Link} for torrent {_torrent.RdName}: {ex.Message}";
|
||||||
|
Finished = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Unpack(String filePath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using (Stream stream = File.OpenRead(filePath))
|
||||||
|
{
|
||||||
|
using var archive = RarArchive.Open(stream);
|
||||||
|
|
||||||
|
BytesTotal = archive.TotalSize;
|
||||||
|
|
||||||
|
var entries = archive.Entries.Where(entry => !entry.IsDirectory)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
_rarfileStatus = entries.ToDictionary(entry => entry.Key, entry => 0L);
|
||||||
|
_rarCurrentEntry = null;
|
||||||
|
archive.CompressedBytesRead += ArchiveOnCompressedBytesRead;
|
||||||
|
|
||||||
|
var extractPath = _destinationPath;
|
||||||
|
|
||||||
|
if (!entries.Any(m => m.Key.StartsWith(_torrent.RdName + @"\")) && !entries.Any(m => m.Key.StartsWith(_torrent.RdName + @"/")))
|
||||||
|
{
|
||||||
|
extractPath = Path.Combine(_destinationPath, _torrent.RdName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entries.Any(m => m.Key.Contains(".r00")))
|
||||||
|
{
|
||||||
|
extractPath = Path.Combine(extractPath, "Temp");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var entry in entries)
|
||||||
|
{
|
||||||
|
_rarCurrentEntry = entry;
|
||||||
|
|
||||||
|
entry.WriteToDirectory(extractPath,
|
||||||
|
new ExtractionOptions
|
||||||
|
{
|
||||||
|
ExtractFullPath = true,
|
||||||
|
Overwrite = true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var retryCount = 0;
|
||||||
|
while (File.Exists(filePath) && retryCount < 10)
|
||||||
|
{
|
||||||
|
retryCount++;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.Delete(filePath);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
await Task.Delay(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Error = $"An unexpected error occurred downloading {_download.Link} for torrent {_torrent.RdName}: {ex.Message}";
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Finished = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ArchiveOnCompressedBytesRead(Object sender, CompressedBytesReadEventArgs e)
|
||||||
|
{
|
||||||
|
if (_rarCurrentEntry == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_rarfileStatus[_rarCurrentEntry.Key] = e.CompressedBytesRead;
|
||||||
|
|
||||||
|
BytesDone = _rarfileStatus.Sum(m => m.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -245,7 +245,7 @@ namespace RdtClient.Web.Controllers
|
||||||
|
|
||||||
foreach (var url in urls)
|
foreach (var url in urls)
|
||||||
{
|
{
|
||||||
await _qBittorrent.TorrentsAdd(url.Trim(), true, false);
|
await _qBittorrent.TorrentsAdd(url.Trim(), true, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
|
|
@ -265,7 +265,7 @@ namespace RdtClient.Web.Controllers
|
||||||
await file.CopyToAsync(target);
|
await file.CopyToAsync(target);
|
||||||
var fileBytes = target.ToArray();
|
var fileBytes = target.ToArray();
|
||||||
|
|
||||||
await _qBittorrent.TorrentsAddFile(fileBytes, true, false);
|
await _qBittorrent.TorrentsAddFile(fileBytes, true, true, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
@ -26,8 +27,15 @@ namespace RdtClient.Web.Controllers
|
||||||
[Route("")]
|
[Route("")]
|
||||||
public async Task<ActionResult<IList<Torrent>>> Get()
|
public async Task<ActionResult<IList<Torrent>>> Get()
|
||||||
{
|
{
|
||||||
var result = await _torrents.Get();
|
var results = await _torrents.Get();
|
||||||
return Ok(result);
|
|
||||||
|
// Prevent infinite recursion when serializing
|
||||||
|
foreach (var file in results.SelectMany(torrent => torrent.Downloads))
|
||||||
|
{
|
||||||
|
file.Torrent = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(results);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
|
|
@ -63,7 +71,7 @@ namespace RdtClient.Web.Controllers
|
||||||
|
|
||||||
var bytes = memoryStream.ToArray();
|
var bytes = memoryStream.ToArray();
|
||||||
|
|
||||||
await _torrents.UploadFile(bytes, formData.AutoDownload, formData.AutoDelete);
|
await _torrents.UploadFile(bytes, formData.AutoDownload, formData.AutoUnpack, formData.AutoDelete);
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
@ -72,16 +80,16 @@ namespace RdtClient.Web.Controllers
|
||||||
[Route("UploadMagnet")]
|
[Route("UploadMagnet")]
|
||||||
public async Task<ActionResult> UploadMagnet([FromBody] TorrentControllerUploadMagnetRequest request)
|
public async Task<ActionResult> UploadMagnet([FromBody] TorrentControllerUploadMagnetRequest request)
|
||||||
{
|
{
|
||||||
await _torrents.UploadMagnet(request.MagnetLink, request.AutoDownload, request.AutoDelete);
|
await _torrents.UploadMagnet(request.MagnetLink, request.AutoDownload, request.AutoUnpack, request.AutoDelete);
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete]
|
[HttpPost]
|
||||||
[Route("{id}")]
|
[Route("Delete/{id}")]
|
||||||
public async Task<ActionResult> Delete(Guid id)
|
public async Task<ActionResult> Delete(Guid id, [FromBody] TorrentControllerDeleteRequest request)
|
||||||
{
|
{
|
||||||
await _torrents.Delete(id);
|
await _torrents.Delete(id, request.DeleteData, request.DeleteRdTorrent, request.DeleteLocalFiles);
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
@ -90,7 +98,7 @@ namespace RdtClient.Web.Controllers
|
||||||
[Route("Download/{id}")]
|
[Route("Download/{id}")]
|
||||||
public async Task<ActionResult> Download(Guid id)
|
public async Task<ActionResult> Download(Guid id)
|
||||||
{
|
{
|
||||||
await _torrents.Download(id);
|
await _torrents.Unrestrict(id);
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
@ -99,6 +107,7 @@ namespace RdtClient.Web.Controllers
|
||||||
public class TorrentControllerUploadFileRequest
|
public class TorrentControllerUploadFileRequest
|
||||||
{
|
{
|
||||||
public Boolean AutoDownload { get; set; }
|
public Boolean AutoDownload { get; set; }
|
||||||
|
public Boolean AutoUnpack { get; set; }
|
||||||
public Boolean AutoDelete { get; set; }
|
public Boolean AutoDelete { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,6 +115,14 @@ namespace RdtClient.Web.Controllers
|
||||||
{
|
{
|
||||||
public String MagnetLink { get; set; }
|
public String MagnetLink { get; set; }
|
||||||
public Boolean AutoDownload { get; set; }
|
public Boolean AutoDownload { get; set; }
|
||||||
|
public Boolean AutoUnpack { get; set; }
|
||||||
public Boolean AutoDelete { get; set; }
|
public Boolean AutoDelete { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class TorrentControllerDeleteRequest
|
||||||
|
{
|
||||||
|
public Boolean DeleteData { get; set; }
|
||||||
|
public Boolean DeleteRdTorrent { get; set; }
|
||||||
|
public Boolean DeleteLocalFiles { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue