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
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
max_line_length = 120
|
||||
|
||||
[*.ts]
|
||||
quote_type = single
|
||||
|
|
|
|||
|
|
@ -1,29 +1,58 @@
|
|||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { FileSizePipe } from 'ngx-filesize';
|
||||
import { TorrentFile } from './models/torrent.model';
|
||||
import { DownloadStatus } from './models/download.model';
|
||||
|
||||
@Pipe({
|
||||
name: 'fileStatus',
|
||||
})
|
||||
export class FileStatusPipe implements PipeTransform {
|
||||
constructor(private pipe: FileSizePipe) {}
|
||||
|
||||
transform(value: TorrentFile): string {
|
||||
if (
|
||||
!value.download ||
|
||||
value.download.status === DownloadStatus.PendingDownload
|
||||
) {
|
||||
if (!value.download) {
|
||||
return 'Pending download';
|
||||
}
|
||||
|
||||
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';
|
||||
}
|
||||
|
||||
if (value.download.status === DownloadStatus.Downloading) {
|
||||
const progress = (
|
||||
(value.download.bytesDownloaded / value.download.bytesSize) *
|
||||
100
|
||||
).toFixed(2);
|
||||
return `${progress || 0}%`;
|
||||
}
|
||||
|
||||
if (value.download.status === DownloadStatus.Finished) {
|
||||
return `Finished`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,17 @@
|
|||
export class Download {
|
||||
public downloadId: string;
|
||||
|
||||
public torrentId: string;
|
||||
|
||||
public link: string;
|
||||
|
||||
public added: Date;
|
||||
|
||||
public status: DownloadStatus;
|
||||
|
||||
public bytesDownloaded: number;
|
||||
|
||||
public bytesSize: number;
|
||||
|
||||
public downloadQueued: Date;
|
||||
public downloadStarted: Date;
|
||||
public downloadFinished: Date;
|
||||
public unpackingQueued: Date;
|
||||
public unpackingStarted: Date;
|
||||
public unpackingFinished: Date;
|
||||
public completed: Date;
|
||||
public error: string;
|
||||
public bytesTotal: number;
|
||||
public bytesDone: number;
|
||||
public speed: number;
|
||||
}
|
||||
|
||||
export enum DownloadStatus {
|
||||
PendingDownload = 0,
|
||||
Downloading = 1,
|
||||
Unpacking = 2,
|
||||
Finished = 3,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
export class Profile {
|
||||
userName: string;
|
||||
expiration: Date;
|
||||
public userName: string;
|
||||
public expiration: Date;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,41 +1,47 @@
|
|||
import { Download } from './download.model';
|
||||
|
||||
export class Torrent {
|
||||
torrentId: string;
|
||||
hash: string;
|
||||
status: TorrentStatus;
|
||||
rdId: string;
|
||||
rdName: string;
|
||||
rdSize: number;
|
||||
rdHost: string;
|
||||
rdSplit: number;
|
||||
rdProgress: number;
|
||||
rdStatus: string;
|
||||
rdAdded: Date;
|
||||
rdEnded: Date;
|
||||
rdSpeed: number;
|
||||
rdSeeders: number;
|
||||
rdFiles: string;
|
||||
public torrentId: string;
|
||||
public hash: string;
|
||||
public category: string;
|
||||
public added: Date;
|
||||
public completed: Date;
|
||||
public autoDownload: boolean;
|
||||
public autoUnpack: boolean;
|
||||
public autoDelete: boolean;
|
||||
|
||||
files: TorrentFile[];
|
||||
downloads: Download[];
|
||||
public rdId: string;
|
||||
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 {
|
||||
id: string;
|
||||
path: string;
|
||||
bytes: number;
|
||||
selected: boolean;
|
||||
public id: string;
|
||||
public path: string;
|
||||
public bytes: number;
|
||||
public selected: boolean;
|
||||
|
||||
download: Download;
|
||||
public download: Download;
|
||||
}
|
||||
|
||||
export enum TorrentStatus {
|
||||
RealDebrid = 0,
|
||||
WaitingForDownload = 1,
|
||||
DownloadQueued = 2,
|
||||
Downloading = 3,
|
||||
Finished = 4,
|
||||
export enum RealDebridStatus {
|
||||
Processing = 0,
|
||||
WaitingForFileSelection = 1,
|
||||
Downloading = 2,
|
||||
Finished = 3,
|
||||
|
||||
Error = 99,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,10 @@
|
|||
<input type="checkbox" [(ngModel)]="autoDownload" />
|
||||
Download torrent to host when finished downloading on Real-Debrid
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" [(ngModel)]="autoUnpack" />
|
||||
Unpack files after downloading to host
|
||||
</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" [(ngModel)]="autoDelete" />
|
||||
Remove torrent when finished downloading on host
|
||||
|
|
@ -61,6 +65,7 @@
|
|||
class="button is-success"
|
||||
[disabled]="saving"
|
||||
[ngClass]="{ 'is-loading': saving }"
|
||||
(click)="ok()"
|
||||
>
|
||||
<span>Add Torrent</span>
|
||||
</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';
|
||||
|
||||
@Component({
|
||||
|
|
@ -25,6 +25,7 @@ export class AddNewTorrentComponent implements OnInit {
|
|||
public fileName: string;
|
||||
public magnetLink: string;
|
||||
public autoDownload: boolean;
|
||||
public autoUnpack: boolean;
|
||||
public autoDelete: boolean;
|
||||
|
||||
public saving = false;
|
||||
|
|
@ -67,7 +68,12 @@ export class AddNewTorrentComponent implements OnInit {
|
|||
|
||||
if (this.magnetLink) {
|
||||
this.torrentService
|
||||
.uploadMagnet(this.magnetLink, this.autoDownload, this.autoDelete)
|
||||
.uploadMagnet(
|
||||
this.magnetLink,
|
||||
this.autoDownload,
|
||||
this.autoUnpack,
|
||||
this.autoDelete
|
||||
)
|
||||
.subscribe(
|
||||
() => {
|
||||
this.cancel();
|
||||
|
|
@ -79,7 +85,12 @@ export class AddNewTorrentComponent implements OnInit {
|
|||
);
|
||||
} else if (this.selectedFile) {
|
||||
this.torrentService
|
||||
.uploadFile(this.selectedFile, this.autoDownload, this.autoDelete)
|
||||
.uploadFile(
|
||||
this.selectedFile,
|
||||
this.autoDownload,
|
||||
this.autoUnpack,
|
||||
this.autoDelete
|
||||
)
|
||||
.subscribe(
|
||||
() => {
|
||||
this.cancel();
|
||||
|
|
|
|||
|
|
@ -42,15 +42,9 @@ export class SettingsComponent implements OnInit {
|
|||
|
||||
this.settingsService.get().subscribe(
|
||||
(results) => {
|
||||
this.settingRealDebridApiKey = this.getSetting(
|
||||
results,
|
||||
'RealDebridApiKey'
|
||||
);
|
||||
this.settingRealDebridApiKey = this.getSetting(results, 'RealDebridApiKey');
|
||||
this.settingDownloadFolder = this.getSetting(results, 'DownloadFolder');
|
||||
this.settingDownloadLimit = parseInt(
|
||||
this.getSetting(results, 'DownloadLimit'),
|
||||
10
|
||||
);
|
||||
this.settingDownloadLimit = parseInt(this.getSetting(results, 'DownloadLimit'), 10);
|
||||
},
|
||||
(err) => {
|
||||
this.error = err.error;
|
||||
|
|
|
|||
|
|
@ -6,22 +6,12 @@
|
|||
{{ torrent | status }}
|
||||
</td>
|
||||
<td>
|
||||
<span
|
||||
class="icon download-icon"
|
||||
(click)="download($event)"
|
||||
title="Download to disk"
|
||||
*ngIf="!loading && torrent.status === 1"
|
||||
>
|
||||
<span class="icon download-icon" (click)="download($event)" title="Download to disk" *ngIf="!loading">
|
||||
<i class="fas fa-download"></i>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span
|
||||
class="icon delete-icon"
|
||||
(click)="delete($event)"
|
||||
title="Delete torrent"
|
||||
*ngIf="!loading"
|
||||
>
|
||||
<span class="icon delete-icon" (click)="delete1($event)" title="Delete torrent" *ngIf="!loading">
|
||||
<i class="fas fa-times"></i>
|
||||
</span>
|
||||
<span class="icon loading-icon" *ngIf="loading">
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { Torrent, TorrentStatus } from 'src/app/models/torrent.model';
|
||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { Torrent } from 'src/app/models/torrent.model';
|
||||
import { TorrentService } from 'src/app/torrent.service';
|
||||
|
||||
@Component({
|
||||
|
|
@ -11,6 +11,9 @@ export class TorrentRowComponent implements OnInit {
|
|||
@Input()
|
||||
public torrent: Torrent;
|
||||
|
||||
@Output('delete')
|
||||
public delete = new EventEmitter();
|
||||
|
||||
public loading = false;
|
||||
|
||||
constructor(private torrentService: TorrentService) {}
|
||||
|
|
@ -23,14 +26,11 @@ export class TorrentRowComponent implements OnInit {
|
|||
this.loading = true;
|
||||
this.torrentService.download(this.torrent.torrentId).subscribe(() => {
|
||||
this.loading = false;
|
||||
this.torrent.status = TorrentStatus.Downloading;
|
||||
});
|
||||
}
|
||||
|
||||
public delete(event: Event): void {
|
||||
public delete1(event: Event): void {
|
||||
event.stopPropagation();
|
||||
|
||||
this.loading = true;
|
||||
this.torrentService.delete(this.torrent.torrentId).subscribe(() => {});
|
||||
this.delete.emit(this.torrent.torrentId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { FileSizePipe } from 'ngx-filesize';
|
||||
import { DownloadStatus } from './models/download.model';
|
||||
import { Torrent, TorrentStatus } from './models/torrent.model';
|
||||
import { RealDebridStatus, Torrent } from './models/torrent.model';
|
||||
|
||||
@Pipe({
|
||||
name: 'status',
|
||||
|
|
@ -10,68 +9,72 @@ export class TorrentStatusPipe implements PipeTransform {
|
|||
constructor(private pipe: FileSizePipe) {}
|
||||
|
||||
transform(torrent: Torrent): string {
|
||||
if (torrent.completed) {
|
||||
return 'Finished';
|
||||
}
|
||||
|
||||
if (torrent.downloads && torrent.downloads.length > 0) {
|
||||
const allFinished = torrent.downloads.all(
|
||||
(m) => m.status === DownloadStatus.Finished
|
||||
);
|
||||
const allFinished = torrent.downloads.all((m) => m.completed != null);
|
||||
if (allFinished) {
|
||||
return 'Finished';
|
||||
}
|
||||
|
||||
const downloading = torrent.downloads.where(
|
||||
(m) => m.status === DownloadStatus.Downloading
|
||||
);
|
||||
const unpacking = torrent.downloads.where(
|
||||
(m) => m.status === DownloadStatus.Unpacking
|
||||
);
|
||||
const downloading = torrent.downloads.where((m) => m.downloadStarted && !m.downloadFinished);
|
||||
const unpacking = torrent.downloads.where((m) => m.unpackingStarted && !m.unpackingFinished);
|
||||
|
||||
const allBytesDownloaded = torrent.downloads.sum(
|
||||
(m) => m.bytesDownloaded
|
||||
);
|
||||
const allBytesSize = torrent.downloads.sum((m) => m.bytesSize);
|
||||
let downloadText = '';
|
||||
let unpackText = '';
|
||||
|
||||
let progress = 0;
|
||||
let allSpeeds = 0;
|
||||
|
||||
if (allBytesSize > 0) {
|
||||
progress = (allBytesDownloaded / allBytesSize) * 100;
|
||||
allSpeeds = downloading.sum((m) => m.speed) / downloading.length;
|
||||
}
|
||||
if (downloading.length > 0) {
|
||||
const bytesDone = downloading.sum((m) => m.bytesDone);
|
||||
const bytesTotal = downloading.sum((m) => m.bytesTotal);
|
||||
let progress = (bytesDone / bytesTotal) * 100;
|
||||
let 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 (allBytesSize > 0) {
|
||||
return `Downloading (${progress.toFixed(2)}% - ${speed}/s)`;
|
||||
downloadText = `Downloading (${progress.toFixed(2)}% - ${speed}/s)`;
|
||||
}
|
||||
|
||||
return `Preparing download`;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
switch (torrent.status) {
|
||||
case TorrentStatus.RealDebrid:
|
||||
if (result.length > 0) {
|
||||
return result.join('\r\n');
|
||||
}
|
||||
}
|
||||
|
||||
switch (torrent.rdStatus) {
|
||||
case RealDebridStatus.Downloading:
|
||||
const speed = this.pipe.transform(torrent.rdSpeed, 'filesize');
|
||||
return `Torrent downloading (${torrent.rdProgress}% - ${speed}/s)`;
|
||||
case TorrentStatus.WaitingForDownload:
|
||||
return `Ready to download, press the download icon to start`;
|
||||
case TorrentStatus.DownloadQueued:
|
||||
return `Download queued`;
|
||||
case TorrentStatus.Downloading:
|
||||
return `Downloading`;
|
||||
case TorrentStatus.Finished:
|
||||
return `Finished`;
|
||||
case TorrentStatus.Error:
|
||||
return `Error`;
|
||||
case RealDebridStatus.Processing:
|
||||
return `Torrent processing`;
|
||||
case RealDebridStatus.WaitingForFileSelection:
|
||||
return `Torrent waiting for file selection`;
|
||||
case RealDebridStatus.Error:
|
||||
return `Torrent error: ${torrent.rdStatusRaw}`;
|
||||
case RealDebridStatus.Finished:
|
||||
return `Torrent finished`;
|
||||
default:
|
||||
return 'Unknown status';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@
|
|||
<div class="table-container">
|
||||
<table class="table is-fullwidth is-hoverable">
|
||||
<colgroup>
|
||||
<col style="width: 50%;" />
|
||||
<col style="width: 15%;" />
|
||||
<col style="width: 35%;" />
|
||||
<col style="width: 50px;" />
|
||||
<col style="width: 50px;" />
|
||||
<col style="width: 50%" />
|
||||
<col style="width: 15%" />
|
||||
<col style="width: 35%" />
|
||||
<col style="width: 50px" />
|
||||
<col style="width: 50px" />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
|
|
@ -26,27 +26,70 @@
|
|||
app-torrent-row
|
||||
[torrent]="torrent"
|
||||
(click)="selectTorrent(torrent)"
|
||||
(delete)="showDeleteModal($event)"
|
||||
></tr>
|
||||
|
||||
<ng-container
|
||||
*ngIf="showFiles[torrent.torrentId] && torrent?.files.length === 0"
|
||||
>
|
||||
<ng-container *ngIf="showFiles[torrent.torrentId] && torrent?.files.length === 0">
|
||||
<tr>
|
||||
<td colspan="20">
|
||||
<i class="fas fa-spinner fa-pulse"></i>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<ng-container
|
||||
*ngIf="showFiles[torrent.torrentId] && torrent?.files.length > 0"
|
||||
>
|
||||
<tr
|
||||
app-torrent-file
|
||||
[file]="file"
|
||||
*ngFor="let file of torrent.files"
|
||||
></tr>
|
||||
<ng-container *ngIf="showFiles[torrent.torrentId] && torrent?.files.length > 0">
|
||||
<tr app-torrent-file [file]="file" *ngFor="let file of torrent.files"></tr>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="modal" [class.is-active]="isDeleteModalActive">
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">Delete torrent</p>
|
||||
<button class="delete" aria-label="close" (click)="deleteCancel()"></button>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
<div class="field">
|
||||
<label class="label"></label>
|
||||
<div class="control">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" [(ngModel)]="deleteData" />
|
||||
Delete Torrent from local RealDebrid Client
|
||||
</label>
|
||||
<br />
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" [(ngModel)]="deleteRdTorrent" />
|
||||
Delete Torrent from RealDebrid
|
||||
</label>
|
||||
<br />
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" [(ngModel)]="deleteLocalFiles" />
|
||||
Delete local files
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="notification is-primary">
|
||||
Deleting a torrent from RealDebrid will automatically delete it here too.
|
||||
</div>
|
||||
<div class="notification is-danger is-light" *ngIf="deleteError?.length > 0">
|
||||
Error deleting torrent: {{ deleteError }}
|
||||
</div>
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
<button
|
||||
class="button is-success"
|
||||
(click)="deleteOk()"
|
||||
[disabled]="deleting"
|
||||
[ngClass]="{ 'is-loading': deleting }"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
<button class="button" (click)="deleteCancel()" [disabled]="deleting" [ngClass]="{ 'is-loading': deleting }">
|
||||
Cancel
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,6 @@
|
|||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
HostListener,
|
||||
ElementRef,
|
||||
} from '@angular/core';
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Torrent } from '../models/torrent.model';
|
||||
import { TorrentService } from '../torrent.service';
|
||||
import { DownloadStatus } from '../models/download.model';
|
||||
|
||||
@Component({
|
||||
selector: 'app-torrent-table',
|
||||
|
|
@ -19,6 +12,14 @@ export class TorrentTableComponent implements OnInit, OnDestroy {
|
|||
public error: string;
|
||||
public showFiles: { [key: string]: boolean } = {};
|
||||
|
||||
public isDeleteModalActive: boolean;
|
||||
public deleteError: string;
|
||||
public deleting: boolean;
|
||||
public deleteTorrentId: string;
|
||||
public deleteData: boolean;
|
||||
public deleteRdTorrent: boolean;
|
||||
public deleteLocalFiles: boolean;
|
||||
|
||||
private timer: any;
|
||||
|
||||
constructor(private torrentService: TorrentService) {}
|
||||
|
|
@ -50,9 +51,7 @@ export class TorrentTableComponent implements OnInit, OnDestroy {
|
|||
torrent.downloads = result.downloads;
|
||||
|
||||
torrent.files.forEach((file) => {
|
||||
const downloads = torrent.downloads.filter(
|
||||
(m) => m.link === file.path
|
||||
);
|
||||
const downloads = torrent.downloads.filter((m) => m.link === file.path);
|
||||
|
||||
if (downloads.length > 0) {
|
||||
file.download = downloads[0];
|
||||
|
|
@ -65,4 +64,30 @@ export class TorrentTableComponent implements OnInit, OnDestroy {
|
|||
public trackByMethod(index: number, el: Torrent): string {
|
||||
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 { Observable, Subject } from 'rxjs';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Torrent } from './models/torrent.model';
|
||||
|
||||
@Injectable({
|
||||
|
|
@ -20,23 +20,21 @@ export class TorrentService {
|
|||
public uploadMagnet(
|
||||
magnetLink: string,
|
||||
autoDownload: boolean,
|
||||
autoUnpack: boolean,
|
||||
autoDelete: boolean
|
||||
): Observable<void> {
|
||||
return this.http.post<void>(`/Api/Torrents/UploadMagnet`, {
|
||||
magnetLink,
|
||||
autoDownload,
|
||||
autoUnpack,
|
||||
autoDelete,
|
||||
});
|
||||
}
|
||||
|
||||
public uploadFile(
|
||||
file: File,
|
||||
autoDownload: boolean,
|
||||
autoDelete: boolean
|
||||
): Observable<void> {
|
||||
public uploadFile(file: File, autoDownload: boolean, autoUnpack: boolean, autoDelete: boolean): Observable<void> {
|
||||
const formData: FormData = new FormData();
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -44,7 +42,16 @@ export class TorrentService {
|
|||
return this.http.get<void>(`/Api/Torrents/Download/${torrentId}`);
|
||||
}
|
||||
|
||||
public delete(torrentId: string): Observable<void> {
|
||||
return this.http.delete<void>(`/Api/Torrents/${torrentId}`);
|
||||
public delete(
|
||||
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",
|
||||
Type = "String",
|
||||
#if DEBUG
|
||||
Value = @"C:\Temp\rdtclient"
|
||||
#else
|
||||
Value = "/data/downloads"
|
||||
#endif
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RdtClient.Data.Enums;
|
||||
using RdtClient.Data.Models.Data;
|
||||
|
||||
namespace RdtClient.Data.Data
|
||||
|
|
@ -12,8 +11,14 @@ namespace RdtClient.Data.Data
|
|||
{
|
||||
Task<IList<Download>> Get();
|
||||
Task<IList<Download>> GetForTorrent(Guid torrentId);
|
||||
Task<Download> GetById(Guid downloadId);
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -42,6 +47,14 @@ namespace RdtClient.Data.Data
|
|||
.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)
|
||||
{
|
||||
var download = new Download
|
||||
|
|
@ -50,7 +63,7 @@ namespace RdtClient.Data.Data
|
|||
TorrentId = torrentId,
|
||||
Link = link,
|
||||
Added = DateTimeOffset.UtcNow,
|
||||
Status = DownloadStatus.PendingDownload
|
||||
DownloadQueued = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
await _dataContext.Downloads.AddAsync(download);
|
||||
|
|
@ -60,11 +73,63 @@ namespace RdtClient.Data.Data
|
|||
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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RdtClient.Data.Enums;
|
||||
using RdtClient.Data.Models.Data;
|
||||
|
||||
namespace RdtClient.Data.Data
|
||||
|
|
@ -11,13 +9,13 @@ namespace RdtClient.Data.Data
|
|||
public interface ITorrentData
|
||||
{
|
||||
Task<IList<Torrent>> Get();
|
||||
Task<Torrent> GetById(Guid id);
|
||||
Task<Torrent> GetById(Guid torrentId);
|
||||
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 UpdateStatus(Guid torrentId, TorrentStatus status);
|
||||
Task UpdateCategory(Guid torrentId, String category);
|
||||
Task Delete(Guid id);
|
||||
Task UpdateComplete(Guid torrentId, DateTimeOffset datetime);
|
||||
Task Delete(Guid torrentId);
|
||||
}
|
||||
|
||||
public class TorrentData : ITorrentData
|
||||
|
|
@ -36,20 +34,15 @@ namespace RdtClient.Data.Data
|
|||
.Include(m => m.Downloads)
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var file in results.SelectMany(torrent => torrent.Downloads))
|
||||
{
|
||||
file.Torrent = null;
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public async Task<Torrent> GetById(Guid id)
|
||||
public async Task<Torrent> GetById(Guid torrentId)
|
||||
{
|
||||
var dbTorrent = await _dataContext.Torrents
|
||||
.AsNoTracking()
|
||||
.Include(m => m.Downloads)
|
||||
.FirstOrDefaultAsync(m => m.TorrentId == id);
|
||||
.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
|
||||
|
||||
if (dbTorrent == null)
|
||||
{
|
||||
|
|
@ -84,15 +77,16 @@ namespace RdtClient.Data.Data
|
|||
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
|
||||
{
|
||||
TorrentId = Guid.NewGuid(),
|
||||
Added = DateTimeOffset.UtcNow,
|
||||
RdId = realDebridId,
|
||||
Hash = hash.ToLower(),
|
||||
Status = TorrentStatus.RealDebrid,
|
||||
AutoDownload = autoDownload,
|
||||
AutoUnpack = autoUnpack,
|
||||
AutoDelete = autoDelete
|
||||
};
|
||||
|
||||
|
|
@ -118,6 +112,7 @@ namespace RdtClient.Data.Data
|
|||
dbTorrent.RdSplit = torrent.RdSplit;
|
||||
dbTorrent.RdProgress = torrent.RdProgress;
|
||||
dbTorrent.RdStatus = torrent.RdStatus;
|
||||
dbTorrent.RdStatusRaw = torrent.RdStatusRaw;
|
||||
dbTorrent.RdAdded = torrent.RdAdded;
|
||||
dbTorrent.RdEnded = torrent.RdEnded;
|
||||
dbTorrent.RdSpeed = torrent.RdSpeed;
|
||||
|
|
@ -131,20 +126,6 @@ namespace RdtClient.Data.Data
|
|||
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)
|
||||
{
|
||||
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
|
||||
|
|
@ -159,9 +140,18 @@ namespace RdtClient.Data.Data
|
|||
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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
namespace RdtClient.Data.Enums
|
||||
{
|
||||
public enum DownloadStatus
|
||||
{
|
||||
PendingDownload = 0,
|
||||
Downloading,
|
||||
Unpacking,
|
||||
Finished
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,11 @@
|
|||
namespace RdtClient.Data.Enums
|
||||
{
|
||||
public enum TorrentStatus
|
||||
public enum RealDebridStatus
|
||||
{
|
||||
RealDebrid = 0,
|
||||
WaitingForDownload = 1,
|
||||
DownloadQueued = 2,
|
||||
Downloading = 3,
|
||||
Finished = 4,
|
||||
Processing = 0,
|
||||
WaitingForFileSelection = 1,
|
||||
Downloading = 2,
|
||||
Finished = 3,
|
||||
|
||||
Error = 99
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ using RdtClient.Data.Data;
|
|||
namespace RdtClient.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(DataContext))]
|
||||
[Migration("20201212204128_Initial")]
|
||||
[Migration("20210110025102_Initial")]
|
||||
partial class Initial
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
|
|
@ -219,15 +219,36 @@ namespace RdtClient.Data.Migrations
|
|||
b.Property<DateTimeOffset>("Added")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset?>("Completed")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset?>("DownloadFinished")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset?>("DownloadQueued")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset?>("DownloadStarted")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Error")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Link")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid>("TorrentId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset?>("UnpackingFinished")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset?>("UnpackingQueued")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset?>("UnpackingStarted")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("DownloadId");
|
||||
|
||||
b.HasIndex("TorrentId");
|
||||
|
|
@ -257,15 +278,24 @@ namespace RdtClient.Data.Migrations
|
|||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("Added")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("AutoDelete")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("AutoDownload")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("AutoUnpack")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Category")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset?>("Completed")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Hash")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
|
|
@ -302,12 +332,12 @@ namespace RdtClient.Data.Migrations
|
|||
b.Property<long>("RdSplit")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("RdStatus")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Status")
|
||||
b.Property<int>("RdStatus")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("RdStatusRaw")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("TorrentId");
|
||||
|
||||
b.ToTable("Torrents");
|
||||
|
|
@ -66,16 +66,19 @@ namespace RdtClient.Data.Migrations
|
|||
TorrentId = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
Hash = 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),
|
||||
AutoUnpack = 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),
|
||||
RdName = table.Column<string>(type: "TEXT", nullable: true),
|
||||
RdSize = table.Column<long>(type: "INTEGER", nullable: false),
|
||||
RdHost = table.Column<string>(type: "TEXT", nullable: true),
|
||||
RdSplit = 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),
|
||||
RdEnded = table.Column<DateTimeOffset>(type: "TEXT", 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),
|
||||
Link = table.Column<string>(type: "TEXT", nullable: true),
|
||||
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 =>
|
||||
{
|
||||
|
|
@ -217,15 +217,36 @@ namespace RdtClient.Data.Migrations
|
|||
b.Property<DateTimeOffset>("Added")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset?>("Completed")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset?>("DownloadFinished")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset?>("DownloadQueued")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset?>("DownloadStarted")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Error")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Link")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid>("TorrentId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset?>("UnpackingFinished")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset?>("UnpackingQueued")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset?>("UnpackingStarted")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("DownloadId");
|
||||
|
||||
b.HasIndex("TorrentId");
|
||||
|
|
@ -255,15 +276,24 @@ namespace RdtClient.Data.Migrations
|
|||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("Added")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("AutoDelete")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("AutoDownload")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("AutoUnpack")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Category")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset?>("Completed")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Hash")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
|
|
@ -300,12 +330,12 @@ namespace RdtClient.Data.Migrations
|
|||
b.Property<long>("RdSplit")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("RdStatus")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Status")
|
||||
b.Property<int>("RdStatus")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("RdStatusRaw")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("TorrentId");
|
||||
|
||||
b.ToTable("Torrents");
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using RdtClient.Data.Enums;
|
||||
|
||||
namespace RdtClient.Data.Models.Data
|
||||
{
|
||||
|
|
@ -16,16 +15,30 @@ namespace RdtClient.Data.Models.Data
|
|||
|
||||
public DateTimeOffset Added { get; set; }
|
||||
|
||||
public DownloadStatus Status { get; set; }
|
||||
public DateTimeOffset? DownloadQueued { 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")]
|
||||
public Torrent Torrent { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public Int64 BytesSize { get; set; }
|
||||
public Int64 BytesTotal { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public Int64 BytesDownloaded { get; set; }
|
||||
public Int64 BytesDone { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public Int64 Speed { get; set; }
|
||||
|
|
|
|||
|
|
@ -17,12 +17,13 @@ namespace RdtClient.Data.Models.Data
|
|||
|
||||
public String Category { get; set; }
|
||||
|
||||
public DateTimeOffset Added { get; set; }
|
||||
public DateTimeOffset? Completed { get; set; }
|
||||
|
||||
public Boolean AutoDownload { get; set; }
|
||||
|
||||
public Boolean AutoUnpack { get; set; }
|
||||
public Boolean AutoDelete { get; set; }
|
||||
|
||||
public TorrentStatus Status { get; set; }
|
||||
|
||||
[InverseProperty("Torrent")]
|
||||
public IList<Download> Downloads { get; set; }
|
||||
|
||||
|
|
@ -32,7 +33,8 @@ namespace RdtClient.Data.Models.Data
|
|||
public String RdHost { get; set; }
|
||||
public Int64 RdSplit { 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? RdEnded { get; set; }
|
||||
public Int64? RdSpeed { get; set; }
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ namespace RdtClient.Service
|
|||
services.AddScoped<IQBittorrent, QBittorrent>();
|
||||
services.AddScoped<ISettings, Settings>();
|
||||
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">
|
||||
<PropertyGroup>
|
||||
<ActiveDebugProfile>IIS Express</ActiveDebugProfile>
|
||||
<ShowAllFiles>false</ShowAllFiles>
|
||||
</PropertyGroup>
|
||||
</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.Threading.Tasks;
|
||||
using RdtClient.Data.Data;
|
||||
using RdtClient.Data.Enums;
|
||||
using RdtClient.Data.Models.Data;
|
||||
|
||||
namespace RdtClient.Service.Services
|
||||
|
|
@ -11,8 +10,14 @@ namespace RdtClient.Service.Services
|
|||
{
|
||||
Task<IList<Download>> Get();
|
||||
Task<IList<Download>> GetForTorrent(Guid torrentId);
|
||||
Task<Download> GetById(Guid downloadId);
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -35,14 +40,44 @@ namespace RdtClient.Service.Services
|
|||
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)
|
||||
{
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ namespace RdtClient.Service.Services
|
|||
Task<IList<TorrentFileItem>> TorrentFileContents(String hash);
|
||||
Task<TorrentProperties> TorrentProperties(String hash);
|
||||
Task TorrentsDelete(String hash, Boolean deleteFiles);
|
||||
Task TorrentsAdd(String magnetLink, Boolean autoDownload, Boolean autoDelete);
|
||||
Task TorrentsAddFile(Byte[] fileBytes, Boolean autoDownload, Boolean autoDelete);
|
||||
Task TorrentsAdd(String magnetLink, Boolean autoDownload, Boolean autoUnpack, Boolean autoDelete);
|
||||
Task TorrentsAddFile(Byte[] fileBytes, Boolean autoDownload, Boolean autoUnpack, Boolean autoDelete);
|
||||
Task TorrentsSetCategory(String hash, String category);
|
||||
Task<IDictionary<String, TorrentCategory>> TorrentsCategories();
|
||||
}
|
||||
|
|
@ -275,14 +275,13 @@ namespace RdtClient.Service.Services
|
|||
result.Uploaded = (Int64) (torrent.RdSize * (torrent.RdProgress / 100.0));
|
||||
result.UploadedSession = (Int64) (torrent.RdSize * (torrent.RdProgress / 100.0));
|
||||
result.Upspeed = torrent.RdSpeed ?? 0;
|
||||
result.State = torrent.Status switch
|
||||
result.State = torrent.RdStatus switch
|
||||
{
|
||||
TorrentStatus.RealDebrid => "downloading",
|
||||
TorrentStatus.WaitingForDownload => "downloading",
|
||||
TorrentStatus.DownloadQueued => "downloading",
|
||||
TorrentStatus.Downloading => "downloading",
|
||||
TorrentStatus.Finished => "pausedUP",
|
||||
TorrentStatus.Error => "error",
|
||||
RealDebridStatus.Processing => "downloading",
|
||||
RealDebridStatus.WaitingForFileSelection => "downloading",
|
||||
RealDebridStatus.Downloading => "downloading",
|
||||
RealDebridStatus.Finished => "pausedUP",
|
||||
RealDebridStatus.Error => "error",
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
|
|
@ -304,8 +303,11 @@ namespace RdtClient.Service.Services
|
|||
|
||||
foreach (var file in torrent.Files)
|
||||
{
|
||||
var result = new TorrentFileItem();
|
||||
result.Name = file.Path;
|
||||
var result = new TorrentFileItem
|
||||
{
|
||||
Name = file.Path
|
||||
};
|
||||
|
||||
results.Add(result);
|
||||
}
|
||||
|
||||
|
|
@ -340,7 +342,7 @@ namespace RdtClient.Service.Services
|
|||
Peers = 0,
|
||||
PeersTotal = 2,
|
||||
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,
|
||||
Reannounce = 0,
|
||||
SavePath = savePath,
|
||||
|
|
@ -372,7 +374,7 @@ namespace RdtClient.Service.Services
|
|||
return;
|
||||
}
|
||||
|
||||
await _torrents.Delete(torrent.TorrentId);
|
||||
await _torrents.Delete(torrent.TorrentId, true, true, true);
|
||||
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -1,157 +1,53 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using RdtClient.Data.Enums;
|
||||
|
||||
namespace RdtClient.Service.Services
|
||||
{
|
||||
public class TaskRunner : BackgroundService
|
||||
{
|
||||
public static readonly ConcurrentDictionary<Guid, DownloadManager> ActiveDownloads = new ConcurrentDictionary<Guid, DownloadManager>();
|
||||
|
||||
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;
|
||||
_services = services;
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
#if DEBUG
|
||||
await Task.Delay(TimeSpan.FromSeconds(3), stoppingToken);
|
||||
#else
|
||||
await Task.Delay(TimeSpan.FromSeconds(15), stoppingToken);
|
||||
#endif
|
||||
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
var torrentRunner = scope.ServiceProvider.GetRequiredService<ITorrentRunner>();
|
||||
|
||||
_logger.LogInformation("TaskRunner started.");
|
||||
|
||||
await torrentRunner.Initialize();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
_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.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -16,24 +17,28 @@ namespace RdtClient.Service.Services
|
|||
public interface ITorrents
|
||||
{
|
||||
Task<IList<Torrent>> Get();
|
||||
Task<Torrent> GetById(Guid id);
|
||||
Task<Torrent> GetById(Guid torrentId);
|
||||
Task<Torrent> GetByHash(String hash);
|
||||
Task<IList<Torrent>> Update();
|
||||
Task UpdateStatus(Guid torrentId, TorrentStatus status);
|
||||
Task UpdateCategory(String hash, String category);
|
||||
Task UploadMagnet(String magnetLink, Boolean autoDownload, Boolean autoDelete);
|
||||
Task UploadFile(Byte[] bytes, Boolean autoDownload, Boolean autoDelete);
|
||||
Task Delete(Guid id);
|
||||
Task Download(Guid id);
|
||||
Task UploadMagnet(String magnetLink, Boolean autoDownload, Boolean autoUnpack, Boolean autoDelete);
|
||||
Task UploadFile(Byte[] bytes, Boolean autoDownload, Boolean autoUnpack, Boolean autoDelete);
|
||||
Task SelectFiles(String torrentId, IList<String> fileIds);
|
||||
Task Delete(Guid torrentId, Boolean deleteData, Boolean deleteRdTorrent, Boolean deleteLocalFiles);
|
||||
Task Unrestrict(Guid torrentId);
|
||||
Task Download(Guid downloadId);
|
||||
Task Unpack(Guid downloadId);
|
||||
void Reset();
|
||||
Task<Profile> GetProfile();
|
||||
Task UpdateComplete(Guid torrentId, DateTimeOffset datetime);
|
||||
Task Update();
|
||||
}
|
||||
|
||||
public class Torrents : ITorrents
|
||||
{
|
||||
private static RdNetClient _rdtClient;
|
||||
private static RdNetClient _rdtNetClient;
|
||||
|
||||
private static readonly SemaphoreSlim SemaphoreSlim = new SemaphoreSlim(1, 1);
|
||||
|
||||
private readonly IDownloads _downloads;
|
||||
private readonly ISettings _settings;
|
||||
private readonly ITorrentData _torrentData;
|
||||
|
|
@ -45,11 +50,9 @@ namespace RdtClient.Service.Services
|
|||
_downloads = downloads;
|
||||
}
|
||||
|
||||
private RdNetClient RdNetClient
|
||||
private RdNetClient GetRdNetClient()
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_rdtClient == null)
|
||||
if (_rdtNetClient == null)
|
||||
{
|
||||
var apiKey = _settings.GetString("RealDebridApiKey")
|
||||
.Result;
|
||||
|
|
@ -59,11 +62,10 @@ namespace RdtClient.Service.Services
|
|||
throw new Exception("RealDebrid API Key not set in the settings");
|
||||
}
|
||||
|
||||
_rdtClient = new RdNetClient("X245A4XAIBGVM", null, null, null, apiKey);
|
||||
_rdtNetClient = new RdNetClient("X245A4XAIBGVM", null, null, null, apiKey);
|
||||
}
|
||||
|
||||
return _rdtClient;
|
||||
}
|
||||
return _rdtNetClient;
|
||||
}
|
||||
|
||||
public async Task<IList<Torrent>> Get()
|
||||
|
|
@ -74,16 +76,17 @@ namespace RdtClient.Service.Services
|
|||
{
|
||||
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.BytesSize = activeDownload.BytesSize;
|
||||
download.BytesDownloaded = activeDownload.BytesDownloaded;
|
||||
|
||||
if (activeDownload.NewStatus.HasValue)
|
||||
{
|
||||
download.Status = activeDownload.NewStatus.Value;
|
||||
download.Speed = downloadClient.Speed;
|
||||
download.BytesTotal = downloadClient.BytesTotal;
|
||||
download.BytesDone = downloadClient.BytesDone;
|
||||
}
|
||||
|
||||
if (TorrentRunner.ActiveUnpackClients.TryGetValue(download.DownloadId, out var unpackClient))
|
||||
{
|
||||
download.BytesTotal = unpackClient.BytesTotal;
|
||||
download.BytesDone = unpackClient.BytesDone;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -91,13 +94,13 @@ namespace RdtClient.Service.Services
|
|||
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)
|
||||
{
|
||||
var rdTorrent = await RdNetClient.GetTorrentInfoAsync(torrent.RdId);
|
||||
var rdTorrent = await GetRdNetClient().GetTorrentInfoAsync(torrent.RdId);
|
||||
|
||||
await Update(torrent, rdTorrent);
|
||||
}
|
||||
|
|
@ -111,7 +114,7 @@ namespace RdtClient.Service.Services
|
|||
|
||||
if (torrent != null)
|
||||
{
|
||||
var rdTorrent = await RdNetClient.GetTorrentInfoAsync(torrent.RdId);
|
||||
var rdTorrent = await GetRdNetClient().GetTorrentInfoAsync(torrent.RdId);
|
||||
|
||||
await Update(torrent, rdTorrent);
|
||||
}
|
||||
|
|
@ -119,20 +122,224 @@ namespace RdtClient.Service.Services
|
|||
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);
|
||||
|
||||
if (!w)
|
||||
{
|
||||
return await _torrentData.Get();
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var torrents = await _torrentData.Get();
|
||||
var torrents = await Get();
|
||||
|
||||
try
|
||||
{
|
||||
var rdTorrents = await RdNetClient.GetTorrentsAsync(0, 100);
|
||||
var rdTorrents = await GetRdNetClient().GetTorrentsAsync(0, 100);
|
||||
|
||||
foreach (var rdTorrent in rdTorrents)
|
||||
{
|
||||
|
|
@ -140,13 +347,12 @@ namespace RdtClient.Service.Services
|
|||
|
||||
if (torrent == null)
|
||||
{
|
||||
var newTorrent = await _torrentData.Add(rdTorrent.Id, rdTorrent.Hash, false, false);
|
||||
await GetById(newTorrent.TorrentId);
|
||||
continue;
|
||||
}
|
||||
else if (rdTorrent.Files == null)
|
||||
if (rdTorrent.Files == null)
|
||||
{
|
||||
var rdTorrent2 = await RdNetClient.GetTorrentInfoAsync(rdTorrent.Id);
|
||||
await Update(torrent, rdTorrent2);
|
||||
var rdTorrentInfo = await GetRdNetClient().GetTorrentInfoAsync(rdTorrent.Id);
|
||||
await Update(torrent, rdTorrentInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -164,8 +370,6 @@ namespace RdtClient.Service.Services
|
|||
await _torrentData.Delete(torrent.TorrentId);
|
||||
}
|
||||
}
|
||||
|
||||
return torrents;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
await _torrentData.UpdateComplete(torrentId, datetime);
|
||||
}
|
||||
|
||||
private async Task Update(Torrent torrent, RDNET.Torrent rdTorrent)
|
||||
|
|
@ -325,47 +410,29 @@ namespace RdtClient.Service.Services
|
|||
torrent.RdHost = rdTorrent.Host;
|
||||
torrent.RdSplit = rdTorrent.Split;
|
||||
torrent.RdProgress = rdTorrent.Progress;
|
||||
torrent.RdStatus = rdTorrent.Status;
|
||||
torrent.RdAdded = rdTorrent.Added;
|
||||
torrent.RdEnded = rdTorrent.Ended;
|
||||
torrent.RdSpeed = rdTorrent.Speed;
|
||||
torrent.RdSeeders = rdTorrent.Seeders;
|
||||
torrent.RdStatusRaw = rdTorrent.Status;
|
||||
|
||||
await _torrentData.UpdateRdData(torrent);
|
||||
|
||||
if (torrent.Status == TorrentStatus.RealDebrid)
|
||||
torrent.RdStatus = rdTorrent.Status switch
|
||||
{
|
||||
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
|
||||
"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.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);
|
||||
}
|
||||
}
|
||||
await _torrentData.UpdateRdData(torrent);
|
||||
}
|
||||
}
|
||||
}
|
||||
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)
|
||||
{
|
||||
await _qBittorrent.TorrentsAdd(url.Trim(), true, false);
|
||||
await _qBittorrent.TorrentsAdd(url.Trim(), true, true, false);
|
||||
}
|
||||
|
||||
return Ok();
|
||||
|
|
@ -265,7 +265,7 @@ namespace RdtClient.Web.Controllers
|
|||
await file.CopyToAsync(target);
|
||||
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.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
|
@ -26,8 +27,15 @@ namespace RdtClient.Web.Controllers
|
|||
[Route("")]
|
||||
public async Task<ActionResult<IList<Torrent>>> Get()
|
||||
{
|
||||
var result = await _torrents.Get();
|
||||
return Ok(result);
|
||||
var results = await _torrents.Get();
|
||||
|
||||
// Prevent infinite recursion when serializing
|
||||
foreach (var file in results.SelectMany(torrent => torrent.Downloads))
|
||||
{
|
||||
file.Torrent = null;
|
||||
}
|
||||
|
||||
return Ok(results);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
|
|
@ -63,7 +71,7 @@ namespace RdtClient.Web.Controllers
|
|||
|
||||
var bytes = memoryStream.ToArray();
|
||||
|
||||
await _torrents.UploadFile(bytes, formData.AutoDownload, formData.AutoDelete);
|
||||
await _torrents.UploadFile(bytes, formData.AutoDownload, formData.AutoUnpack, formData.AutoDelete);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
|
@ -72,16 +80,16 @@ namespace RdtClient.Web.Controllers
|
|||
[Route("UploadMagnet")]
|
||||
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();
|
||||
}
|
||||
|
||||
[HttpDelete]
|
||||
[Route("{id}")]
|
||||
public async Task<ActionResult> Delete(Guid id)
|
||||
[HttpPost]
|
||||
[Route("Delete/{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();
|
||||
}
|
||||
|
|
@ -90,7 +98,7 @@ namespace RdtClient.Web.Controllers
|
|||
[Route("Download/{id}")]
|
||||
public async Task<ActionResult> Download(Guid id)
|
||||
{
|
||||
await _torrents.Download(id);
|
||||
await _torrents.Unrestrict(id);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
|
@ -99,6 +107,7 @@ namespace RdtClient.Web.Controllers
|
|||
public class TorrentControllerUploadFileRequest
|
||||
{
|
||||
public Boolean AutoDownload { get; set; }
|
||||
public Boolean AutoUnpack { get; set; }
|
||||
public Boolean AutoDelete { get; set; }
|
||||
}
|
||||
|
||||
|
|
@ -106,6 +115,14 @@ namespace RdtClient.Web.Controllers
|
|||
{
|
||||
public String MagnetLink { get; set; }
|
||||
public Boolean AutoDownload { get; set; }
|
||||
public Boolean AutoUnpack { 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 Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
|
|
|
|||
Loading…
Reference in a new issue