Add AllDebrid support.
This commit is contained in:
parent
723cc82b11
commit
8774243d2f
32 changed files with 548 additions and 228 deletions
|
|
@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.9.7] - 2021-10-30
|
||||
### Added
|
||||
- Add AllDebrid support.
|
||||
|
||||
## [1.9.6] - 2021-10-30
|
||||
### Added
|
||||
- Improved handling of errors on the torrent itself
|
||||
|
|
|
|||
12
README.md
12
README.md
|
|
@ -1,16 +1,20 @@
|
|||
# Real-Debrid Torrent Client
|
||||
|
||||
This is a web interface to manage your torrents on Real-Debrid. It supports the following features:
|
||||
This is a web interface to manage your torrents on Real-Debrid or AllDebrid. It supports the following features:
|
||||
|
||||
- Add new torrents through magnets or files
|
||||
- Download all files from Real Debrid to your local machine automatically
|
||||
- Download all files from Real Debrid or AllDebrid to your local machine automatically
|
||||
- Unpack all files when finished downloading
|
||||
- Implements a fake qBittorrent API so you can hook up other applications like Sonarr or Couchpotato.
|
||||
- Built with Angular 11 and .NET 5
|
||||
|
||||
**You will need a Premium service at Real-Debrid!**
|
||||
**You will need a Premium service at Real-Debrid or AllDebrid!**
|
||||
|
||||
[Click here to sign up (referal link so I can get a few free premium days).](https://real-debrid.com/?id=1348683)
|
||||
[Click here to sign up at Real-Debrid.](https://real-debrid.com/?id=1348683)
|
||||
|
||||
[Click here to sign up AllDebrid.](https://real-debrid.com/?id=1348683)
|
||||
|
||||
<sub>(referal links so I can get a few free premium days)</sub>
|
||||
|
||||
## Docker Setup
|
||||
|
||||
|
|
|
|||
|
|
@ -39,12 +39,29 @@
|
|||
<div class="field">
|
||||
<label class="label">Download action</label>
|
||||
<div class="control select is-fullwidth">
|
||||
<select [(ngModel)]="downloadAction">
|
||||
<option [ngValue]="0">Download all files above a certain size</option>
|
||||
<option [ngValue]="1">Download all available files on Real-Debrid above a certain size</option>
|
||||
<option [ngValue]="2">Pick files I want to download</option>
|
||||
<select [(ngModel)]="downloadAction" [disabled]="provider === 'AllDebrid'">
|
||||
<option [ngValue]="0">Download all files</option>
|
||||
<option [ngValue]="1">Download all available files</option>
|
||||
<option [ngValue]="2">Manually pick files</option>
|
||||
</select>
|
||||
</div>
|
||||
<p class="help" *ngIf="provider === 'AllDebrid'">
|
||||
This option is only available for RealDebrid. AllDebrid will always download the full torrent.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="notification is-success is-light"
|
||||
*ngIf="provider === 'AllDebrid' && availableFiles && availableFiles.length > 0"
|
||||
>
|
||||
This torrent is available for immediate download.
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="notification is-warning is-light"
|
||||
*ngIf="provider === 'AllDebrid' && availableFiles && availableFiles.length === 0"
|
||||
>
|
||||
This torrent is not available for immediate download.
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
|
|
@ -59,6 +76,10 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help" *ngIf="provider === 'AllDebrid'">
|
||||
When downloading with AllDebrid it cannot be guaranteed that only files above this limit will be download as
|
||||
some files are grouped together in 1 large archive.
|
||||
</p>
|
||||
<p class="help" *ngIf="downloadAction === 2">This setting does not apply to manually selected files.</p>
|
||||
</div>
|
||||
|
||||
|
|
@ -93,28 +114,14 @@
|
|||
<div class="field">
|
||||
<label class="label">Automatic retry downloads</label>
|
||||
<div class="control">
|
||||
<input
|
||||
class="input"
|
||||
type="number"
|
||||
max="1000"
|
||||
min="0"
|
||||
step="1"
|
||||
[(ngModel)]="downloadRetryAttempts"
|
||||
/>
|
||||
<input class="input" type="number" max="1000" min="0" step="1" [(ngModel)]="downloadRetryAttempts" />
|
||||
</div>
|
||||
<p class="help">When a single download fails it will retry it this many times before marking it as failed.</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Automatic retry torrent</label>
|
||||
<div class="control">
|
||||
<input
|
||||
class="input"
|
||||
type="number"
|
||||
max="1000"
|
||||
min="0"
|
||||
step="1"
|
||||
[(ngModel)]="torrentRetryAttempts"
|
||||
/>
|
||||
<input class="input" type="number" max="1000" min="0" step="1" [(ngModel)]="torrentRetryAttempts" />
|
||||
</div>
|
||||
<p class="help">
|
||||
When a single download has failed multiple times (see setting above) or when the torrent itself received an
|
||||
|
|
@ -122,12 +129,12 @@
|
|||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div fxFlex>
|
||||
<div fxFlex *ngIf="provider === 'RealDebrid'">
|
||||
<div class="field">
|
||||
<label class="label">Available files</label>
|
||||
<p class="help">
|
||||
These files are available for immediate download from Real-Debrid. <br />
|
||||
It is possible that there are more files in the torrent, which are not shown here.
|
||||
It is possible that there are more files in the torrent, which are not shown here.<br />
|
||||
</p>
|
||||
<div class="scroll-container">
|
||||
<div class="field" *ngIf="downloadAction === 2">
|
||||
|
|
@ -136,7 +143,7 @@
|
|||
Select all
|
||||
</label>
|
||||
</div>
|
||||
<div class="field" *ngIf="downloadAction === 2">
|
||||
<div class="field" *ngIf="downloadAction === 2 && availableFiles != null">
|
||||
<label class="checkbox is-fullwidth-label" *ngFor="let file of availableFiles">
|
||||
<input
|
||||
type="checkbox"
|
||||
|
|
@ -146,9 +153,10 @@
|
|||
{{ file.filename }} ({{ file.filesize | filesize }})
|
||||
</label>
|
||||
</div>
|
||||
<div class="field" *ngIf="downloadAction !== 2">
|
||||
<div class="field" *ngIf="downloadAction !== 2 && availableFiles != null">
|
||||
<label class="is-fullwidth-label is-block" *ngFor="let file of availableFiles">
|
||||
{{ file.filename }} ({{ file.filesize | filesize }})
|
||||
{{ file.filename }}
|
||||
<span *ngIf="file.filesize > 0">({{ file.filesize | filesize }})</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { Component, OnInit } from '@angular/core';
|
|||
import { Router } from '@angular/router';
|
||||
import { TorrentService } from 'src/app/torrent.service';
|
||||
import { Torrent, TorrentFileAvailability } from '../models/torrent.model';
|
||||
import { SettingsService } from '../settings.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-add-new-torrent',
|
||||
|
|
@ -13,6 +14,8 @@ export class AddNewTorrentComponent implements OnInit {
|
|||
public magnetLink: string;
|
||||
private currentTorrentFile: string;
|
||||
|
||||
public provider: string;
|
||||
|
||||
public category: string;
|
||||
public priority: number;
|
||||
|
||||
|
|
@ -24,7 +27,7 @@ export class AddNewTorrentComponent implements OnInit {
|
|||
public downloadRetryAttempts: number = 3;
|
||||
public torrentRetryAttempts: number = 1;
|
||||
|
||||
public availableFiles: TorrentFileAvailability[] = [];
|
||||
public availableFiles: TorrentFileAvailability[];
|
||||
public downloadFiles: { [key: string]: boolean } = {};
|
||||
public allSelected: boolean;
|
||||
|
||||
|
|
@ -33,7 +36,11 @@ export class AddNewTorrentComponent implements OnInit {
|
|||
|
||||
private selectedFile: File;
|
||||
|
||||
constructor(private router: Router, private torrentService: TorrentService) {}
|
||||
constructor(private router: Router, private torrentService: TorrentService, private settingsService: SettingsService) {
|
||||
this.settingsService.get().subscribe(settings => {
|
||||
this.provider = settings.firstOrDefault(m => m.settingId === 'Provider')?.value;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {}
|
||||
|
||||
|
|
@ -143,7 +150,7 @@ export class AddNewTorrentComponent implements OnInit {
|
|||
|
||||
this.saving = true;
|
||||
this.error = null;
|
||||
this.availableFiles = [];
|
||||
this.availableFiles = null;
|
||||
this.downloadFiles = {};
|
||||
this.allSelected = true;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
export class Profile {
|
||||
public provider: string;
|
||||
public userName: string;
|
||||
public expiration: Date;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
</span>
|
||||
<span>Settings</span>
|
||||
</a>
|
||||
<a class="navbar-item" *ngIf="profile" href="https://real-debrid.com/?id=1348683" target="_blank" rel="noopener">
|
||||
<a class="navbar-item" *ngIf="profile" href="{{providerLink}}" target="_blank" rel="noopener">
|
||||
<span class="icon">
|
||||
<i class="fas fa-euro-sign" aria-hidden="true"></i>
|
||||
</span>
|
||||
|
|
@ -55,7 +55,7 @@
|
|||
<a class="navbar-item"> Profile </a>
|
||||
<a class="navbar-item" (click)="logout()"> Logout </a>
|
||||
<hr class="navbar-divider" />
|
||||
<div class="navbar-item">Version 1.9.6</div>
|
||||
<div class="navbar-item">Version 1.9.7</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -13,12 +13,22 @@ export class NavbarComponent implements OnInit {
|
|||
public showMobileMenu = false;
|
||||
|
||||
public profile: Profile;
|
||||
public providerLink: string;
|
||||
|
||||
constructor(private settingsService: SettingsService, private authService: AuthService, private router: Router) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.settingsService.getProfile().subscribe((result) => {
|
||||
this.profile = result;
|
||||
|
||||
switch (result.provider) {
|
||||
case 'RealDebrid':
|
||||
this.providerLink = 'https://real-debrid.com/?id=1348683';
|
||||
break;
|
||||
case 'AllDebrid':
|
||||
this.providerLink = 'https://alldebrid.com/?uid=2v91l&lang=en';
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@
|
|||
<li [ngClass]="{ 'is-active': activeTab === 0 }" (click)="activeTab = 0">
|
||||
<a>General</a>
|
||||
</li>
|
||||
<li [ngClass]="{ 'is-active': activeTab === 4 }" (click)="activeTab = 4">
|
||||
<a>Provider</a>
|
||||
</li>
|
||||
<li [ngClass]="{ 'is-active': activeTab === 1 }" (click)="activeTab = 1">
|
||||
<a>Download Client</a>
|
||||
</li>
|
||||
|
|
@ -15,8 +18,26 @@
|
|||
</ul>
|
||||
</div>
|
||||
|
||||
<div *ngIf="activeTab === 0">
|
||||
<div *ngIf="activeTab === 4">
|
||||
<div class="field">
|
||||
<label class="label">Provider</label>
|
||||
<div class="control select is-fullwidth">
|
||||
<select [(ngModel)]="settingProvider">
|
||||
<option value="RealDebrid">Real-Debrid</option>
|
||||
<option value="AllDebrid">AllDebrid</option>
|
||||
</select>
|
||||
</div>
|
||||
<p class="help">
|
||||
The following 2 providers are supported:
|
||||
<br />
|
||||
<a href="https://real-debrid.com/?id=1348683" target="_blank" rel="noopener">https://real-debrid.com</a>
|
||||
<br />
|
||||
<a href="https://alldebrid.com/?uid=2v91l&lang=en" target="_blank" rel="noopener">https://alldebrid.com</a>
|
||||
<br/>
|
||||
At this point only 1 provider can be used at the time.
|
||||
</p>
|
||||
</div>
|
||||
<div class="field" *ngIf="settingProvider === 'RealDebrid'">
|
||||
<label class="label">Real-Debrid API Key</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" maxlength="100" [(ngModel)]="settingRealDebridApiKey" />
|
||||
|
|
@ -26,7 +47,19 @@
|
|||
<a href="https://real-debrid.com/apitoken" target="_blank" rel="noopener">https://real-debrid.com/apitoken</a>.
|
||||
</p>
|
||||
</div>
|
||||
<div class="field" *ngIf="settingProvider === 'AllDebrid'">
|
||||
<label class="label">AllDebrid API Key</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" maxlength="100" [(ngModel)]="settingRealDebridApiKey" />
|
||||
</div>
|
||||
<p class="help">
|
||||
You can find your API key here:
|
||||
<a href="https://alldebrid.com/apikeys/" target="_blank" rel="noopener">https://alldebrid.com/apikeys/</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="activeTab === 0">
|
||||
<div class="field">
|
||||
<label class="label">Log level</label>
|
||||
<div class="control select is-fullwidth">
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ export class SettingsComponent implements OnInit {
|
|||
public testAria2cConnectionSuccess: string = null;
|
||||
|
||||
public settingLogLevel: string;
|
||||
public settingProvider: string;
|
||||
public settingRealDebridApiKey: string;
|
||||
public settingDownloadPath: string;
|
||||
public settingMappedPath: string;
|
||||
|
|
@ -57,6 +58,7 @@ export class SettingsComponent implements OnInit {
|
|||
|
||||
this.settingsService.get().subscribe(
|
||||
(results) => {
|
||||
this.settingProvider = this.getSetting(results, 'Provider');
|
||||
this.settingRealDebridApiKey = this.getSetting(results, 'RealDebridApiKey');
|
||||
this.settingLogLevel = this.getSetting(results, 'LogLevel');
|
||||
this.settingDownloadPath = this.getSetting(results, 'DownloadPath');
|
||||
|
|
@ -86,6 +88,10 @@ export class SettingsComponent implements OnInit {
|
|||
this.saving = true;
|
||||
|
||||
const settings: Setting[] = [
|
||||
{
|
||||
settingId: 'Provider',
|
||||
value: this.settingProvider,
|
||||
},
|
||||
{
|
||||
settingId: 'RealDebridApiKey',
|
||||
value: this.settingRealDebridApiKey,
|
||||
|
|
|
|||
|
|
@ -28,11 +28,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<button
|
||||
class="button is-success"
|
||||
[ngClass]="{ 'is-loading': working }"
|
||||
[disabled]="working"
|
||||
>
|
||||
<button class="button is-success" [ngClass]="{ 'is-loading': working }" [disabled]="working">
|
||||
Setup
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -46,25 +42,46 @@
|
|||
<div class="notification is-primary">
|
||||
To be able to use the Real-Debrid Client you need a premium subscription to download torrents.
|
||||
<br /><br />
|
||||
Not premium yet?
|
||||
Not premium yet? You have the choice of 2 providers:
|
||||
<br/>
|
||||
<a href="https://real-debrid.com/?id=1348683" target="_blank" rel="noopener"
|
||||
>Use this link to sign up to Real-Debrid.</a
|
||||
>
|
||||
<br /><br />
|
||||
To connect to Real-Debrid please enter your Personal Prive API Token found here:
|
||||
<a href="https://real-debrid.com/apitoken" target="_blank" rel="noopener"
|
||||
>https://real-debrid.com/apitoken</a
|
||||
>.
|
||||
<br/>
|
||||
<a href="https://alldebrid.com/?uid=2v91l&lang=en" target="_blank" rel="noopener"
|
||||
>Use this link to sign up to AllDebrid.</a
|
||||
>
|
||||
<br/>
|
||||
<small>(Referal links)</small>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Provider</label>
|
||||
<div class="control select is-fullwidth">
|
||||
<select [(ngModel)]="provider">
|
||||
<option value="RealDebrid">Real-Debrid</option>
|
||||
<option value="AllDebrid">AllDebrid</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="" class="label">API Private Token</label>
|
||||
<div class="control has-icons-left">
|
||||
<div class="control">
|
||||
<input type="text" placeholder="" class="input" name="token" [(ngModel)]="token" required />
|
||||
<span class="icon is-small is-left">
|
||||
<i class="fa fa-envelope"></i>
|
||||
</span>
|
||||
</div>
|
||||
<p class="help" *ngIf="provider === 'RealDebrid'">
|
||||
You can find your API key here:
|
||||
<a href="https://real-debrid.com/apitoken" target="_blank" rel="noopener"
|
||||
>https://real-debrid.com/apitoken</a
|
||||
>.
|
||||
</p>
|
||||
<p class="help" *ngIf="provider === 'AllDebrid'">
|
||||
You can find your API key here:
|
||||
<a href="https://alldebrid.com/apikeys/" target="_blank" rel="noopener"
|
||||
>https://alldebrid.com/apikeys/</a
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<button
|
||||
class="button is-success"
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { SettingsService } from '../settings.service';
|
|||
export class SetupComponent implements OnInit {
|
||||
public userName: string;
|
||||
public password: string;
|
||||
public provider = 'RealDebrid';
|
||||
public token: string;
|
||||
|
||||
public error: string;
|
||||
|
|
@ -40,11 +41,15 @@ export class SetupComponent implements OnInit {
|
|||
}
|
||||
|
||||
public setToken(): void {
|
||||
const setting = new Setting();
|
||||
setting.settingId = 'RealDebridApiKey';
|
||||
setting.value = this.token;
|
||||
const settingToken = new Setting();
|
||||
settingToken.settingId = 'RealDebridApiKey';
|
||||
settingToken.value = this.token;
|
||||
|
||||
this.settingsService.update([setting]).subscribe(
|
||||
const settingProvider = new Setting();
|
||||
settingProvider.settingId = 'Provider';
|
||||
settingProvider.value = this.provider;
|
||||
|
||||
this.settingsService.update([settingToken, settingProvider]).subscribe(
|
||||
() => {
|
||||
this.step = 3;
|
||||
this.working = false;
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ export class TorrentService {
|
|||
public uploadFile(file: File, torrent: Torrent): Observable<void> {
|
||||
const formData: FormData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('formData', JSON.stringify(torrent));
|
||||
formData.append('formData', JSON.stringify({ torrent }));
|
||||
return this.http.post<void>(`/Api/Torrents/UploadFile`, formData);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class UsersService {
|
||||
constructor() {}
|
||||
|
||||
public getLoggedInUser() {}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "rdt-client",
|
||||
"version": "1.9.6",
|
||||
"version": "1.9.7",
|
||||
"description": "This is a web interface to manage your torrents on Real-Debrid.",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
|
|
|
|||
|
|
@ -40,6 +40,12 @@ namespace RdtClient.Data.Data
|
|||
{
|
||||
var seedSettings = new List<Setting>
|
||||
{
|
||||
new Setting
|
||||
{
|
||||
SettingId = "Provider",
|
||||
Type = "String",
|
||||
Value = "RealDebrid"
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "RealDebridApiKey",
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ namespace RdtClient.Data.Data
|
|||
|
||||
Get = new DbSettings
|
||||
{
|
||||
Provider = GetString("Provider"),
|
||||
RealDebridApiKey = GetString("RealDebridApiKey"),
|
||||
DownloadPath = GetString("DownloadPath"),
|
||||
DownloadClient = GetString("DownloadClient"),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
namespace RdtClient.Data.Enums
|
||||
{
|
||||
public enum RealDebridStatus
|
||||
public enum TorrentStatus
|
||||
{
|
||||
Processing = 0,
|
||||
WaitingForFileSelection = 1,
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ namespace RdtClient.Data.Models.Data
|
|||
public String RdHost { get; set; }
|
||||
public Int64 RdSplit { get; set; }
|
||||
public Int64 RdProgress { get; set; }
|
||||
public RealDebridStatus RdStatus { get; set; }
|
||||
public TorrentStatus RdStatus { get; set; }
|
||||
public String RdStatusRaw { get; set; }
|
||||
public DateTimeOffset RdAdded { get; set; }
|
||||
public DateTimeOffset? RdEnded { get; set; }
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ namespace RdtClient.Data.Models.Internal
|
|||
{
|
||||
public class DbSettings
|
||||
{
|
||||
public String Provider { get; set; }
|
||||
public String RealDebridApiKey { get; set; }
|
||||
public String DownloadPath { get; set; }
|
||||
public String DownloadClient { get; set; }
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ namespace RdtClient.Data.Models.Internal
|
|||
{
|
||||
public class Profile
|
||||
{
|
||||
public String Provider { get; set; }
|
||||
public String UserName { get; set; }
|
||||
public DateTimeOffset? Expiration { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ namespace RdtClient.Data.Models.TorrentClient
|
|||
public Int64 Split { get; set; }
|
||||
public Int64 Progress { get; set; }
|
||||
public String Status { get; set; }
|
||||
public Int64 StatusCode { get; set; }
|
||||
public DateTimeOffset Added { get; set; }
|
||||
public List<TorrentClientFile> Files { get; set; }
|
||||
public List<String> Links { get; set; }
|
||||
|
|
|
|||
|
|
@ -5,6 +5,6 @@ namespace RdtClient.Data.Models.TorrentClient
|
|||
public class TorrentClientUser
|
||||
{
|
||||
public String Username { get; set; }
|
||||
public DateTimeOffset Expiration { get; set; }
|
||||
public DateTimeOffset? Expiration { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ namespace RdtClient.Service
|
|||
{
|
||||
public static void Config(IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<AllDebridTorrentClient>();
|
||||
services.AddScoped<Authentication>();
|
||||
services.AddScoped<Downloads>();
|
||||
services.AddScoped<QBittorrent>();
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AllDebrid.NET" Version="1.0.1" />
|
||||
<PackageReference Include="AllDebrid.NET" Version="1.0.2" />
|
||||
<PackageReference Include="Aria2.NET" Version="1.0.4" />
|
||||
<PackageReference Include="Downloader" Version="2.2.9" />
|
||||
<PackageReference Include="MonoTorrent" Version="2.0.1" />
|
||||
|
|
|
|||
|
|
@ -307,7 +307,7 @@ namespace RdtClient.Service.Services
|
|||
var allDownloadsComplete = torrent.Downloads.All(m => m.Completed.HasValue);
|
||||
var hasDownloadsWithErrors = torrent.Downloads.Any(m => m.Error != null);
|
||||
|
||||
if (torrent.Downloads.Count == 0 || hasDownloadsWithErrors || torrent.RdStatus == RealDebridStatus.Error)
|
||||
if (torrent.Downloads.Count == 0 || hasDownloadsWithErrors || torrent.RdStatus == TorrentStatus.Error)
|
||||
{
|
||||
result.State = "error";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,16 +4,23 @@ using System.Linq;
|
|||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using AllDebridNET;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using RdtClient.Data.Enums;
|
||||
using RdtClient.Data.Models.Data;
|
||||
using RdtClient.Data.Models.TorrentClient;
|
||||
using RdtClient.Service.Helpers;
|
||||
|
||||
namespace RdtClient.Service.Services.TorrentClients
|
||||
{
|
||||
public class AllDebridTorrentClient : ITorrentClient
|
||||
{
|
||||
private readonly ILogger<AllDebridTorrentClient> _logger;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
|
||||
public AllDebridTorrentClient(IHttpClientFactory httpClientFactory)
|
||||
public AllDebridTorrentClient(ILogger<AllDebridTorrentClient> logger, IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
_logger = logger;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
}
|
||||
|
||||
|
|
@ -48,13 +55,14 @@ namespace RdtClient.Service.Services.TorrentClients
|
|||
Split = 0,
|
||||
Progress = torrent.ProcessingPerc,
|
||||
Status = torrent.Status,
|
||||
StatusCode = torrent.StatusCode,
|
||||
Added = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(torrent.UploadDate),
|
||||
Files = (torrent.Links ?? new List<Link>()).Select(m => new TorrentClientFile
|
||||
Files = (torrent.Links ?? new List<Link>()).Select((m, i) => new TorrentClientFile
|
||||
{
|
||||
Path = m.Filename,
|
||||
Bytes = m.Size,
|
||||
Id = 0,
|
||||
Selected = true
|
||||
Id = i,
|
||||
Selected = true,
|
||||
}).ToList(),
|
||||
Links = (torrent.Links ?? new List<Link>()).Select(m => m.LinkUrl.ToString()).ToList(),
|
||||
Ended = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(torrent.CompletionDate),
|
||||
|
|
@ -76,7 +84,7 @@ namespace RdtClient.Service.Services.TorrentClients
|
|||
return new TorrentClientUser
|
||||
{
|
||||
Username = user.Username,
|
||||
Expiration = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(user.PremiumUntil)
|
||||
Expiration = user.PremiumUntil > 0 ? new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(user.PremiumUntil) : null
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -94,12 +102,26 @@ namespace RdtClient.Service.Services.TorrentClients
|
|||
return result.Id.ToString();
|
||||
}
|
||||
|
||||
public Task<List<TorrentClientAvailableFile>> GetAvailableFiles(String hash)
|
||||
public async Task<IList<TorrentClientAvailableFile>> GetAvailableFiles(String hash)
|
||||
{
|
||||
return Task.FromResult(new List<TorrentClientAvailableFile>());
|
||||
var isAvailable = await GetClient().Magnet.InstantAvailabilityAsync(hash);
|
||||
|
||||
if (isAvailable)
|
||||
{
|
||||
return new List<TorrentClientAvailableFile>
|
||||
{
|
||||
new TorrentClientAvailableFile
|
||||
{
|
||||
Filename = "All files",
|
||||
Filesize = 0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return new List<TorrentClientAvailableFile>();
|
||||
}
|
||||
|
||||
public Task SelectFiles(String torrentId, IList<String> fileIds)
|
||||
public Task SelectFiles(Torrent torrent)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
|
@ -122,5 +144,136 @@ namespace RdtClient.Service.Services.TorrentClients
|
|||
|
||||
return result.Link;
|
||||
}
|
||||
|
||||
public async Task<Torrent> UpdateData(Torrent torrent, TorrentClientTorrent torrentClientTorrent)
|
||||
{
|
||||
try
|
||||
{
|
||||
torrentClientTorrent ??= await GetInfo(torrent.RdId);
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(torrentClientTorrent.Filename))
|
||||
{
|
||||
torrent.RdName = torrentClientTorrent.Filename;
|
||||
}
|
||||
|
||||
if (torrentClientTorrent.Bytes > 0)
|
||||
{
|
||||
torrent.RdSize = torrentClientTorrent.Bytes;
|
||||
}
|
||||
|
||||
if (torrentClientTorrent.Files != null)
|
||||
{
|
||||
torrent.RdFiles = JsonConvert.SerializeObject(torrentClientTorrent.Files);
|
||||
}
|
||||
|
||||
torrent.RdHost = torrentClientTorrent.Host;
|
||||
torrent.RdSplit = torrentClientTorrent.Split;
|
||||
torrent.RdProgress = torrentClientTorrent.Progress;
|
||||
torrent.RdAdded = torrentClientTorrent.Added;
|
||||
torrent.RdEnded = torrentClientTorrent.Ended;
|
||||
torrent.RdSpeed = torrentClientTorrent.Speed;
|
||||
torrent.RdSeeders = torrentClientTorrent.Seeders;
|
||||
torrent.RdStatusRaw = torrentClientTorrent.Status;
|
||||
|
||||
torrent.RdStatus = torrentClientTorrent.StatusCode switch
|
||||
{
|
||||
0 => TorrentStatus.Processing, // Processing In Queue.
|
||||
1 => TorrentStatus.Downloading, // Processing Downloading.
|
||||
2 => TorrentStatus.Downloading, // Processing Compressing / Moving.
|
||||
3 => TorrentStatus.Uploading, // Processing Uploading.
|
||||
4 => TorrentStatus.Finished, // Finished Ready.
|
||||
5 => TorrentStatus.Error, // Error Upload fail.
|
||||
6 => TorrentStatus.Error, // Error Internal error on unpacking.
|
||||
7 => TorrentStatus.Error, // Error Not downloaded in 20 min.
|
||||
8 => TorrentStatus.Error, // Error File too big.
|
||||
9 => TorrentStatus.Error, // Error Internal error.
|
||||
10 => TorrentStatus.Error, // Error Download took more than 72h.
|
||||
11 => TorrentStatus.Error, // Error Deleted on the hoster website
|
||||
_ => TorrentStatus.Error
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex.Message == "Resource not found")
|
||||
{
|
||||
torrent.RdStatusRaw = "deleted";
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return torrent;
|
||||
}
|
||||
|
||||
public async Task<IList<String>> GetDownloadLinks(Torrent torrent)
|
||||
{
|
||||
var magnet = await GetClient().Magnet.StatusAsync(torrent.RdId);
|
||||
|
||||
var links = magnet.Links;
|
||||
|
||||
Log($"Getting download links", torrent);
|
||||
|
||||
if (torrent.DownloadMinSize > 0)
|
||||
{
|
||||
var minFileSize = torrent.DownloadMinSize * 1024 * 1024;
|
||||
|
||||
Log($"Determining which files are over {minFileSize} bytes", torrent);
|
||||
|
||||
links = links.Where(m => m.Size > minFileSize)
|
||||
.ToList();
|
||||
|
||||
Log($"Found {links.Count} files that match the minimum file size criterea", torrent);
|
||||
}
|
||||
|
||||
if (links.Count == 0)
|
||||
{
|
||||
Log($"Filtered all files out! Downloading ALL files instead!", torrent);
|
||||
|
||||
links = magnet.Links;
|
||||
}
|
||||
|
||||
Log($"Selecting links:");
|
||||
|
||||
foreach (var link in links)
|
||||
{
|
||||
var fileList = link.Files.SelectMany(file => GetFiles(file));
|
||||
|
||||
Log($"{link.Filename} ({link.Size}b) {link.LinkUrl}, contains {String.Join(", ", fileList)}");
|
||||
}
|
||||
|
||||
Log("", torrent);
|
||||
|
||||
return links.Select(m => m.LinkUrl.ToString()).ToList();
|
||||
}
|
||||
|
||||
private static IList<String> GetFiles(File file, String parent = null)
|
||||
{
|
||||
var result = new List<String>
|
||||
{
|
||||
$"{parent}/{file.Name}"
|
||||
};
|
||||
|
||||
if (file.Files != null && file.Files.Count > 0)
|
||||
{
|
||||
foreach (var subFile in file.Files)
|
||||
{
|
||||
result.AddRange(GetFiles(subFile, file.Name));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void Log(String message, Torrent torrent = null)
|
||||
{
|
||||
if (torrent != null)
|
||||
{
|
||||
message = $"{message} {torrent.ToLog()}";
|
||||
}
|
||||
|
||||
_logger.LogDebug(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using RdtClient.Data.Models.Data;
|
||||
using RdtClient.Data.Models.TorrentClient;
|
||||
|
||||
namespace RdtClient.Service.Services.TorrentClients
|
||||
|
|
@ -11,10 +12,12 @@ namespace RdtClient.Service.Services.TorrentClients
|
|||
Task<TorrentClientUser> GetUser();
|
||||
Task<String> AddMagnet(String magnetLink);
|
||||
Task<String> AddFile(Byte[] bytes);
|
||||
Task<List<TorrentClientAvailableFile>> GetAvailableFiles(String hash);
|
||||
Task SelectFiles(String torrentId, IList<String> fileIds);
|
||||
Task<IList<TorrentClientAvailableFile>> GetAvailableFiles(String hash);
|
||||
Task SelectFiles(Torrent torrent);
|
||||
Task<TorrentClientTorrent> GetInfo(String torrentId);
|
||||
Task Delete(String torrentId);
|
||||
Task<String> Unrestrict(String link);
|
||||
Task<Torrent> UpdateData(Torrent torrent, TorrentClientTorrent torrentClientTorrent);
|
||||
Task<IList<String>> GetDownloadLinks(Torrent torrent);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,17 +3,23 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using RDNET;
|
||||
using RdtClient.Data.Enums;
|
||||
using RdtClient.Data.Models.TorrentClient;
|
||||
using RdtClient.Service.Helpers;
|
||||
|
||||
namespace RdtClient.Service.Services.TorrentClients
|
||||
{
|
||||
public class RealDebridTorrentClient : ITorrentClient
|
||||
{
|
||||
private readonly ILogger<RealDebridTorrentClient> _logger;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
|
||||
public RealDebridTorrentClient(IHttpClientFactory httpClientFactory)
|
||||
public RealDebridTorrentClient(ILogger<RealDebridTorrentClient> logger, IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
_logger = logger;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
}
|
||||
|
||||
|
|
@ -93,7 +99,7 @@ namespace RdtClient.Service.Services.TorrentClients
|
|||
return new TorrentClientUser
|
||||
{
|
||||
Username = user.Username,
|
||||
Expiration = user.Expiration
|
||||
Expiration = user.Premium > 0 ? user.Expiration : null
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -111,7 +117,7 @@ namespace RdtClient.Service.Services.TorrentClients
|
|||
return result.Id;
|
||||
}
|
||||
|
||||
public async Task<List<TorrentClientAvailableFile>> GetAvailableFiles(String hash)
|
||||
public async Task<IList<TorrentClientAvailableFile>> GetAvailableFiles(String hash)
|
||||
{
|
||||
var result = await GetClient().Torrents.GetAvailableFiles(hash);
|
||||
|
||||
|
|
@ -128,9 +134,66 @@ namespace RdtClient.Service.Services.TorrentClients
|
|||
return torrentClientAvailableFiles;
|
||||
}
|
||||
|
||||
public async Task SelectFiles(String torrentId, IList<String> fileIds)
|
||||
public async Task SelectFiles(Data.Models.Data.Torrent torrent)
|
||||
{
|
||||
await GetClient().Torrents.SelectFilesAsync(torrentId, fileIds.ToArray());
|
||||
var files = torrent.Files;
|
||||
|
||||
Log("Seleting files", torrent);
|
||||
|
||||
if (torrent.DownloadAction == TorrentDownloadAction.DownloadAvailableFiles)
|
||||
{
|
||||
Log($"Determining which files are already available on RealDebrid", torrent);
|
||||
|
||||
var availableFiles = await GetAvailableFiles(torrent.Hash);
|
||||
|
||||
Log($"Found {files.Count}/{torrent.Files.Count} available files on RealDebrid", torrent);
|
||||
|
||||
files = torrent.Files.Where(m => availableFiles.Any(f => m.Path.EndsWith(f.Filename))).ToList();
|
||||
}
|
||||
else if (torrent.DownloadAction == TorrentDownloadAction.DownloadAll)
|
||||
{
|
||||
Log("Selecting all files", torrent);
|
||||
files = torrent.Files.ToList();
|
||||
}
|
||||
else if (torrent.DownloadAction == TorrentDownloadAction.DownloadManual)
|
||||
{
|
||||
Log("Selecting manual selected files", torrent);
|
||||
files = torrent.Files.Where(m => torrent.ManualFiles.Any(f => m.Path.EndsWith(f))).ToList();
|
||||
}
|
||||
|
||||
Log($"Selecting {files.Count}/{torrent.Files.Count} files", torrent);
|
||||
|
||||
if (torrent.DownloadAction != TorrentDownloadAction.DownloadManual && torrent.DownloadMinSize > 0)
|
||||
{
|
||||
var minFileSize = torrent.DownloadMinSize * 1024 * 1024;
|
||||
|
||||
Log($"Determining which files are over {minFileSize} bytes", torrent);
|
||||
|
||||
files = files.Where(m => m.Bytes > minFileSize)
|
||||
.ToList();
|
||||
|
||||
Log($"Found {files.Count} files that match the minimum file size criterea", torrent);
|
||||
}
|
||||
|
||||
if (files.Count == 0)
|
||||
{
|
||||
Log($"Filtered all files out! Downloading ALL files instead!", torrent);
|
||||
|
||||
files = torrent.Files;
|
||||
}
|
||||
|
||||
var fileIds = files.Select(m => m.Id.ToString()).ToArray();
|
||||
|
||||
Log($"Selecting files:");
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
Log($"{file.Id}: {file.Path} ({file.Bytes}b)");
|
||||
}
|
||||
|
||||
Log("", torrent);
|
||||
|
||||
await GetClient().Torrents.SelectFilesAsync(torrent.RdId, fileIds.ToArray());
|
||||
}
|
||||
|
||||
public async Task<TorrentClientTorrent> GetInfo(String torrentId)
|
||||
|
|
@ -151,5 +214,104 @@ namespace RdtClient.Service.Services.TorrentClients
|
|||
|
||||
return result.Download;
|
||||
}
|
||||
|
||||
public async Task<Data.Models.Data.Torrent> UpdateData(Data.Models.Data.Torrent torrent, TorrentClientTorrent torrentClientTorrent)
|
||||
{
|
||||
try
|
||||
{
|
||||
var rdTorrent = await GetInfo(torrent.RdId);
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(rdTorrent.Filename))
|
||||
{
|
||||
torrent.RdName = rdTorrent.Filename;
|
||||
}
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(rdTorrent.OriginalFilename))
|
||||
{
|
||||
torrent.RdName = rdTorrent.OriginalFilename;
|
||||
}
|
||||
|
||||
if (rdTorrent.Bytes > 0)
|
||||
{
|
||||
torrent.RdSize = rdTorrent.Bytes;
|
||||
}
|
||||
else if (rdTorrent.OriginalBytes > 0)
|
||||
{
|
||||
torrent.RdSize = rdTorrent.OriginalBytes;
|
||||
}
|
||||
|
||||
if (rdTorrent.Files != null)
|
||||
{
|
||||
torrent.RdFiles = JsonConvert.SerializeObject(rdTorrent.Files);
|
||||
}
|
||||
|
||||
torrent.RdHost = rdTorrent.Host;
|
||||
torrent.RdSplit = rdTorrent.Split;
|
||||
torrent.RdProgress = rdTorrent.Progress;
|
||||
torrent.RdAdded = rdTorrent.Added;
|
||||
torrent.RdEnded = rdTorrent.Ended;
|
||||
torrent.RdSpeed = rdTorrent.Speed;
|
||||
torrent.RdSeeders = rdTorrent.Seeders;
|
||||
torrent.RdStatusRaw = rdTorrent.Status;
|
||||
|
||||
torrent.RdStatus = rdTorrent.Status switch
|
||||
{
|
||||
"magnet_error" => TorrentStatus.Error,
|
||||
"magnet_conversion" => TorrentStatus.Processing,
|
||||
"waiting_files_selection" => TorrentStatus.WaitingForFileSelection,
|
||||
"queued" => TorrentStatus.Downloading,
|
||||
"downloading" => TorrentStatus.Downloading,
|
||||
"downloaded" => TorrentStatus.Finished,
|
||||
"error" => TorrentStatus.Error,
|
||||
"virus" => TorrentStatus.Error,
|
||||
"compressing" => TorrentStatus.Downloading,
|
||||
"uploading" => TorrentStatus.Uploading,
|
||||
"dead" => TorrentStatus.Error,
|
||||
_ => TorrentStatus.Error
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex.Message == "Resource not found")
|
||||
{
|
||||
torrent.RdStatusRaw = "deleted";
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return torrent;
|
||||
}
|
||||
|
||||
public async Task<IList<String>> GetDownloadLinks(Data.Models.Data.Torrent torrent)
|
||||
{
|
||||
var rdTorrent = await GetInfo(torrent.RdId);
|
||||
|
||||
var torrentLinks = rdTorrent.Links.Where(m => !String.IsNullOrWhiteSpace(m)).ToList();
|
||||
|
||||
Log($"Found {torrentLinks} links", torrent);
|
||||
|
||||
// Sometimes RD will give you 1 rar with all files, sometimes it will give you 1 link per file.
|
||||
if (torrent.Files.Count(m => m.Selected) != torrentLinks.Count &&
|
||||
torrent.ManualFiles.Count != torrentLinks.Count &&
|
||||
torrentLinks.Count != 1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return torrentLinks;
|
||||
}
|
||||
|
||||
private void Log(String message, Data.Models.Data.Torrent torrent = null)
|
||||
{
|
||||
if (torrent != null)
|
||||
{
|
||||
message = $"{message} {torrent.ToLog()}";
|
||||
}
|
||||
|
||||
_logger.LogDebug(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -473,7 +473,7 @@ namespace RdtClient.Service.Services
|
|||
Log("Processing", torrent);
|
||||
|
||||
// If torrent is erroring out on the Real-Debrid side.
|
||||
if (torrent.RdStatus == RealDebridStatus.Error)
|
||||
if (torrent.RdStatus == TorrentStatus.Error)
|
||||
{
|
||||
Log($"Torrent reported an error: {torrent.RdStatusRaw}", torrent);
|
||||
Log($"Torrent retry count {torrent.RetryCount}/{torrent.TorrentRetryAttempts}", torrent);
|
||||
|
|
@ -486,75 +486,26 @@ namespace RdtClient.Service.Services
|
|||
}
|
||||
|
||||
// Real-Debrid is waiting for file selection, select which files to download.
|
||||
if ((torrent.RdStatus == RealDebridStatus.WaitingForFileSelection || torrent.RdStatus == RealDebridStatus.Finished) &&
|
||||
if ((torrent.RdStatus == TorrentStatus.WaitingForFileSelection || torrent.RdStatus == TorrentStatus.Finished) &&
|
||||
torrent.FilesSelected == null &&
|
||||
torrent.Downloads.Count == 0 &&
|
||||
torrent.FilesSelected == null)
|
||||
torrent.Downloads.Count == 0)
|
||||
{
|
||||
Log($"Selecting files", torrent);
|
||||
|
||||
var files = torrent.Files;
|
||||
|
||||
if (torrent.DownloadAction == TorrentDownloadAction.DownloadAvailableFiles)
|
||||
{
|
||||
Log($"Determining which files are already available on RealDebrid", torrent);
|
||||
|
||||
var availableFiles = await _torrents.GetAvailableFiles(torrent.Hash);
|
||||
|
||||
Log($"Found {files.Count}/{torrent.Files.Count} available files on RealDebrid", torrent);
|
||||
|
||||
files = torrent.Files.Where(m => availableFiles.Any(f => m.Path.EndsWith(f.Filename))).ToList();
|
||||
}
|
||||
else if (torrent.DownloadAction == TorrentDownloadAction.DownloadAll)
|
||||
{
|
||||
Log("Selecting all files", torrent);
|
||||
files = torrent.Files.ToList();
|
||||
}
|
||||
else if (torrent.DownloadAction == TorrentDownloadAction.DownloadManual)
|
||||
{
|
||||
Log("Selecting manual selected files", torrent);
|
||||
files = torrent.Files.Where(m => torrent.ManualFiles.Any(f => m.Path.EndsWith(f))).ToList();
|
||||
}
|
||||
|
||||
Log($"Selecting {files.Count}/{torrent.Files.Count} files", torrent);
|
||||
|
||||
if (torrent.DownloadAction != TorrentDownloadAction.DownloadManual && torrent.DownloadMinSize > 0)
|
||||
{
|
||||
var minFileSize = torrent.DownloadMinSize * 1024 * 1024;
|
||||
|
||||
Log($"Determining which files are over {minFileSize} bytes", torrent);
|
||||
|
||||
files = files.Where(m => m.Bytes > minFileSize)
|
||||
.ToList();
|
||||
|
||||
Log($"Found {files.Count} files that match the minimum file size criterea", torrent);
|
||||
}
|
||||
|
||||
if (files.Count == 0)
|
||||
{
|
||||
Log($"Filtered all files out! Downloading ALL files instead!", torrent);
|
||||
|
||||
files = torrent.Files;
|
||||
}
|
||||
|
||||
var fileIds = files.Select(m => m.Id.ToString()).ToArray();
|
||||
|
||||
Log($"Selecting files:{Environment.NewLine}{String.Join(Environment.NewLine, files.Select(m => m.Path))}", torrent);
|
||||
|
||||
await _torrents.SelectFiles(torrent.TorrentId, fileIds);
|
||||
await _torrents.SelectFiles(torrent.TorrentId);
|
||||
|
||||
await _torrents.UpdateFilesSelected(torrent.TorrentId, DateTime.UtcNow);
|
||||
}
|
||||
|
||||
// Real-Debrid finished downloading the torrent, process the file to host.
|
||||
if (torrent.RdStatus == RealDebridStatus.Finished)
|
||||
if (torrent.RdStatus == TorrentStatus.Finished)
|
||||
{
|
||||
// The files are selected but there are no downloads yet, check if Real-Debrid has generated links yet.
|
||||
if (torrent.Downloads.Count == 0 && torrent.FilesSelected != null)
|
||||
{
|
||||
Log($"Checking for links", torrent);
|
||||
Log($"Creating downloads", torrent);
|
||||
|
||||
await _torrents.CheckForLinks(torrent.TorrentId);
|
||||
await _torrents.CreateDownloads(torrent.TorrentId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ using Microsoft.Extensions.Logging;
|
|||
using MonoTorrent;
|
||||
using Newtonsoft.Json;
|
||||
using RdtClient.Data.Data;
|
||||
using RdtClient.Data.Enums;
|
||||
using RdtClient.Data.Models.Internal;
|
||||
using RdtClient.Data.Models.TorrentClient;
|
||||
using RdtClient.Service.Helpers;
|
||||
|
|
@ -27,18 +26,24 @@ namespace RdtClient.Service.Services
|
|||
|
||||
private readonly ITorrentClient _torrentClient;
|
||||
|
||||
public static readonly SemaphoreSlim TorrentResetLock = new(1, 1);
|
||||
private static readonly SemaphoreSlim TorrentResetLock = new(1, 1);
|
||||
|
||||
public Torrents(ILogger<Torrents> logger,
|
||||
TorrentData torrentData,
|
||||
Downloads downloads,
|
||||
AllDebridTorrentClient allDebridTorrentClient,
|
||||
RealDebridTorrentClient realDebridTorrentClient)
|
||||
{
|
||||
_logger = logger;
|
||||
_torrentData = torrentData;
|
||||
_downloads = downloads;
|
||||
|
||||
_torrentClient = realDebridTorrentClient;
|
||||
_torrentClient = Settings.Get.Provider switch
|
||||
{
|
||||
"RealDebrid" => realDebridTorrentClient,
|
||||
"AllDebrid" => allDebridTorrentClient,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<IList<Torrent>> Get()
|
||||
|
|
@ -73,7 +78,7 @@ namespace RdtClient.Service.Services
|
|||
|
||||
if (torrent != null)
|
||||
{
|
||||
await UpdateRdData(torrent);
|
||||
await UpdateTorrentClientData(torrent);
|
||||
}
|
||||
|
||||
return torrent;
|
||||
|
|
@ -144,14 +149,14 @@ namespace RdtClient.Service.Services
|
|||
return newTorrent;
|
||||
}
|
||||
|
||||
public async Task<List<TorrentClientAvailableFile>> GetAvailableFiles(String hash)
|
||||
public async Task<IList<TorrentClientAvailableFile>> GetAvailableFiles(String hash)
|
||||
{
|
||||
var result = await _torrentClient.GetAvailableFiles(hash);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task SelectFiles(Guid torrentId, IList<String> fileIds)
|
||||
public async Task SelectFiles(Guid torrentId)
|
||||
{
|
||||
var torrent = await GetById(torrentId);
|
||||
|
||||
|
|
@ -160,10 +165,10 @@ namespace RdtClient.Service.Services
|
|||
return;
|
||||
}
|
||||
|
||||
await _torrentClient.SelectFiles(torrent.RdId, fileIds);
|
||||
await _torrentClient.SelectFiles(torrent);
|
||||
}
|
||||
|
||||
public async Task CheckForLinks(Guid torrentId)
|
||||
public async Task CreateDownloads(Guid torrentId)
|
||||
{
|
||||
var torrent = await GetById(torrentId);
|
||||
|
||||
|
|
@ -172,16 +177,9 @@ namespace RdtClient.Service.Services
|
|||
return;
|
||||
}
|
||||
|
||||
var rdTorrent = await _torrentClient.GetInfo(torrent.RdId);
|
||||
var torrentLinks = await _torrentClient.GetDownloadLinks(torrent);
|
||||
|
||||
var torrentLinks = rdTorrent.Links.Where(m => !String.IsNullOrWhiteSpace(m)).ToList();
|
||||
|
||||
Log($"Found {torrentLinks} links", torrent);
|
||||
|
||||
// Sometimes RD will give you 1 rar with all files, sometimes it will give you 1 link per file.
|
||||
if (torrent.Files.Count(m => m.Selected) != torrentLinks.Count &&
|
||||
torrent.ManualFiles.Count != torrentLinks.Count &&
|
||||
torrentLinks.Count != 1)
|
||||
if (torrentLinks == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -313,6 +311,7 @@ namespace RdtClient.Service.Services
|
|||
|
||||
var profile = new Profile
|
||||
{
|
||||
Provider = Settings.Get.Provider,
|
||||
UserName = user.Username,
|
||||
Expiration = user.Expiration
|
||||
};
|
||||
|
|
@ -339,7 +338,7 @@ namespace RdtClient.Service.Services
|
|||
continue;
|
||||
}
|
||||
|
||||
await UpdateRdData(torrent);
|
||||
await UpdateTorrentClientData(torrent, rdTorrent);
|
||||
}
|
||||
|
||||
foreach (var torrent in torrents)
|
||||
|
|
@ -509,7 +508,7 @@ namespace RdtClient.Service.Services
|
|||
return null;
|
||||
}
|
||||
|
||||
await UpdateRdData(torrent);
|
||||
await UpdateTorrentClientData(torrent);
|
||||
|
||||
foreach (var download in torrent.Downloads)
|
||||
{
|
||||
|
|
@ -565,7 +564,7 @@ namespace RdtClient.Service.Services
|
|||
isFile,
|
||||
torrent);
|
||||
|
||||
await UpdateRdData(newTorrent);
|
||||
await UpdateTorrentClientData(newTorrent);
|
||||
|
||||
return newTorrent;
|
||||
}
|
||||
|
|
@ -580,7 +579,7 @@ namespace RdtClient.Service.Services
|
|||
await _torrentData.Update(torrent);
|
||||
}
|
||||
|
||||
private async Task UpdateRdData(Torrent torrent)
|
||||
private async Task UpdateTorrentClientData(Torrent torrent, TorrentClientTorrent torrentClientTorrent = null)
|
||||
{
|
||||
var originalTorrent = JsonConvert.SerializeObject(torrent,
|
||||
new JsonSerializerSettings
|
||||
|
|
@ -588,70 +587,7 @@ namespace RdtClient.Service.Services
|
|||
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
var rdTorrent = await _torrentClient.GetInfo(torrent.RdId);
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(rdTorrent.Filename))
|
||||
{
|
||||
torrent.RdName = rdTorrent.Filename;
|
||||
}
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(rdTorrent.OriginalFilename))
|
||||
{
|
||||
torrent.RdName = rdTorrent.OriginalFilename;
|
||||
}
|
||||
|
||||
if (rdTorrent.Bytes > 0)
|
||||
{
|
||||
torrent.RdSize = rdTorrent.Bytes;
|
||||
}
|
||||
else if (rdTorrent.OriginalBytes > 0)
|
||||
{
|
||||
torrent.RdSize = rdTorrent.OriginalBytes;
|
||||
}
|
||||
|
||||
if (rdTorrent.Files != null)
|
||||
{
|
||||
torrent.RdFiles = JsonConvert.SerializeObject(rdTorrent.Files);
|
||||
}
|
||||
|
||||
torrent.RdHost = rdTorrent.Host;
|
||||
torrent.RdSplit = rdTorrent.Split;
|
||||
torrent.RdProgress = rdTorrent.Progress;
|
||||
torrent.RdAdded = rdTorrent.Added;
|
||||
torrent.RdEnded = rdTorrent.Ended;
|
||||
torrent.RdSpeed = rdTorrent.Speed;
|
||||
torrent.RdSeeders = rdTorrent.Seeders;
|
||||
torrent.RdStatusRaw = rdTorrent.Status;
|
||||
|
||||
torrent.RdStatus = rdTorrent.Status switch
|
||||
{
|
||||
"magnet_error" => RealDebridStatus.Error,
|
||||
"magnet_conversion" => RealDebridStatus.Processing,
|
||||
"waiting_files_selection" => RealDebridStatus.WaitingForFileSelection,
|
||||
"queued" => RealDebridStatus.Downloading,
|
||||
"downloading" => RealDebridStatus.Downloading,
|
||||
"downloaded" => RealDebridStatus.Finished,
|
||||
"error" => RealDebridStatus.Error,
|
||||
"virus" => RealDebridStatus.Error,
|
||||
"compressing" => RealDebridStatus.Downloading,
|
||||
"uploading" => RealDebridStatus.Uploading,
|
||||
"dead" => RealDebridStatus.Error,
|
||||
_ => RealDebridStatus.Error
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex.Message == "Resource not found")
|
||||
{
|
||||
torrent.RdStatusRaw = "deleted";
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
await _torrentClient.UpdateData(torrent, torrentClientTorrent);
|
||||
|
||||
var newTorrent = JsonConvert.SerializeObject(torrent,
|
||||
new JsonSerializerSettings
|
||||
|
|
|
|||
|
|
@ -77,6 +77,15 @@ namespace RdtClient.Web.Controllers
|
|||
[ModelBinder(BinderType = typeof(JsonModelBinder))]
|
||||
TorrentControllerUploadFileRequest formData)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
var errors = ModelState.Select(x => x.Value.Errors)
|
||||
.Where(y => y.Count > 0)
|
||||
.ToList();
|
||||
|
||||
return BadRequest(errors);
|
||||
}
|
||||
|
||||
if (file == null || file.Length <= 0)
|
||||
{
|
||||
throw new Exception("Invalid torrent file");
|
||||
|
|
@ -99,6 +108,15 @@ namespace RdtClient.Web.Controllers
|
|||
[Route("UploadMagnet")]
|
||||
public async Task<ActionResult> UploadMagnet([FromBody] TorrentControllerUploadMagnetRequest request)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
var errors = ModelState.Select(x => x.Value.Errors)
|
||||
.Where(y => y.Count > 0)
|
||||
.ToList();
|
||||
|
||||
return BadRequest(errors);
|
||||
}
|
||||
|
||||
await _torrents.UploadMagnet(request.MagnetLink, request.Torrent);
|
||||
|
||||
return Ok();
|
||||
|
|
@ -152,6 +170,7 @@ namespace RdtClient.Web.Controllers
|
|||
[Route("Retry/{torrentId:guid}")]
|
||||
public async Task<ActionResult> Retry(Guid torrentId)
|
||||
{
|
||||
await _torrents.UpdateRetry(torrentId, DateTimeOffset.UtcNow, 0);
|
||||
await _torrents.RetryTorrent(torrentId, 0);
|
||||
|
||||
return Ok();
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<TargetFramework>net5.0</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<UserSecretsId>94c24cba-f03f-4453-a671-3640b517c573</UserSecretsId>
|
||||
<Version>1.9.6</Version>
|
||||
<Version>1.9.7</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
Loading…
Reference in a new issue