Swapped out the timer GET for a signalr connection.

This commit is contained in:
Roger Far 2021-01-10 20:21:49 -07:00
parent 8f35952f90
commit 21ad2ebfd6
14 changed files with 218 additions and 69 deletions

View file

@ -2,5 +2,10 @@
"/Api": { "/Api": {
"target": "http://localhost:6500/", "target": "http://localhost:6500/",
"secure": false "secure": false
},
"/hub": {
"target": "http://localhost:6500/",
"secure": false,
"ws": true
} }
} }

View file

@ -1618,6 +1618,18 @@
"schema-utils": "^2.7.0" "schema-utils": "^2.7.0"
} }
}, },
"@microsoft/signalr": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/@microsoft/signalr/-/signalr-5.0.1.tgz",
"integrity": "sha512-qk5xMEpEqvxmj2/7ZRkHpWyt33gGXiNTkM+iIFr0QYEaynsbwGUxyV5WhuxcOP9LyXgUx6IXBer2HVAGOC4zpw==",
"requires": {
"abort-controller": "^3.0.0",
"eventsource": "^1.0.7",
"fetch-cookie": "^0.7.3",
"node-fetch": "^2.6.0",
"ws": "^6.0.0"
}
},
"@ngtools/webpack": { "@ngtools/webpack": {
"version": "11.0.5", "version": "11.0.5",
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-11.0.5.tgz", "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-11.0.5.tgz",
@ -2002,6 +2014,14 @@
"integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==",
"dev": true "dev": true
}, },
"abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
"requires": {
"event-target-shim": "^5.0.0"
}
},
"accepts": { "accepts": {
"version": "1.3.7", "version": "1.3.7",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
@ -2333,8 +2353,7 @@
"async-limiter": { "async-limiter": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
"integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="
"dev": true
}, },
"asynckit": { "asynckit": {
"version": "0.4.0", "version": "0.4.0",
@ -4628,6 +4647,11 @@
"next-tick": "~1.0.0" "next-tick": "~1.0.0"
} }
}, },
"es6-denodeify": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/es6-denodeify/-/es6-denodeify-0.1.5.tgz",
"integrity": "sha1-MdTV/pxVA+ElRgQ5MQ4WoqPznB8="
},
"es6-iterator": { "es6-iterator": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
@ -4733,6 +4757,11 @@
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
"dev": true "dev": true
}, },
"event-target-shim": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="
},
"eventemitter3": { "eventemitter3": {
"version": "4.0.7", "version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
@ -4749,7 +4778,6 @@
"version": "1.0.7", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz",
"integrity": "sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==", "integrity": "sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==",
"dev": true,
"requires": { "requires": {
"original": "^1.0.0" "original": "^1.0.0"
} }
@ -5072,6 +5100,15 @@
"websocket-driver": ">=0.5.1" "websocket-driver": ">=0.5.1"
} }
}, },
"fetch-cookie": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-0.7.3.tgz",
"integrity": "sha512-rZPkLnI8x5V+zYAiz8QonAHsTb4BY+iFowFBI1RFn0zrO343AVp9X7/yUj/9wL6Ef/8fLls8b/vGtzUvmyAUGA==",
"requires": {
"es6-denodeify": "^0.1.1",
"tough-cookie": "^2.3.3"
}
},
"figgy-pudding": { "figgy-pudding": {
"version": "3.5.2", "version": "3.5.2",
"resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz",
@ -7801,6 +7838,11 @@
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
"dev": true "dev": true
}, },
"node-fetch": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
},
"node-fetch-npm": { "node-fetch-npm": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.4.tgz", "resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.4.tgz",
@ -8311,7 +8353,6 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz",
"integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==",
"dev": true,
"requires": { "requires": {
"url-parse": "^1.4.3" "url-parse": "^1.4.3"
} }
@ -9883,8 +9924,7 @@
"psl": { "psl": {
"version": "1.8.0", "version": "1.8.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
"integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ=="
"dev": true
}, },
"public-encrypt": { "public-encrypt": {
"version": "4.0.3", "version": "4.0.3",
@ -9944,8 +9984,7 @@
"punycode": { "punycode": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
"dev": true
}, },
"q": { "q": {
"version": "1.5.1", "version": "1.5.1",
@ -9980,8 +10019,7 @@
"querystringify": { "querystringify": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="
"dev": true
}, },
"randombytes": { "randombytes": {
"version": "2.1.0", "version": "2.1.0",
@ -10281,8 +10319,7 @@
"requires-port": { "requires-port": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
"dev": true
}, },
"resolve": { "resolve": {
"version": "1.15.1", "version": "1.15.1",
@ -12004,7 +12041,6 @@
"version": "2.5.0", "version": "2.5.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
"dev": true,
"requires": { "requires": {
"psl": "^1.1.28", "psl": "^1.1.28",
"punycode": "^2.1.1" "punycode": "^2.1.1"
@ -12354,7 +12390,6 @@
"version": "1.4.7", "version": "1.4.7",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz",
"integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==",
"dev": true,
"requires": { "requires": {
"querystringify": "^2.1.1", "querystringify": "^2.1.1",
"requires-port": "^1.0.0" "requires-port": "^1.0.0"
@ -13607,7 +13642,6 @@
"version": "6.2.1", "version": "6.2.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz",
"integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==",
"dev": true,
"requires": { "requires": {
"async-limiter": "~1.0.0" "async-limiter": "~1.0.0"
} }

View file

@ -21,6 +21,7 @@
"@angular/platform-browser-dynamic": "~11.0.5", "@angular/platform-browser-dynamic": "~11.0.5",
"@angular/router": "~11.0.5", "@angular/router": "~11.0.5",
"@fortawesome/fontawesome-free": "^5.15.1", "@fortawesome/fontawesome-free": "^5.15.1",
"@microsoft/signalr": "^5.0.1",
"bulma": "^0.9.1", "bulma": "^0.9.1",
"curray": "^1.0.8", "curray": "^1.0.8",
"ngx-filesize": "^2.0.16", "ngx-filesize": "^2.0.16",

View file

@ -3,6 +3,8 @@
{{ file.path }} {{ file.path }}
</td> </td>
<td>{{ file.bytes | filesize }}</td> <td>{{ file.bytes | filesize }}</td>
<td></td>
<td></td>
<td> <td>
{{ file | fileStatus }} {{ file | fileStatus }}
</td> </td>

View file

@ -9,9 +9,9 @@
{{ torrent.downloads.length | number }} {{ torrent.downloads.length | number }}
</td> </td>
<td class="auto"> <td class="auto">
<i class="fas fa-download" *ngIf="!torrent.autoDownload" title="Auto download"></i> <i class="fas fa-download" *ngIf="torrent.autoDownload" title="Auto download"></i>
<i class="fas fa-box-open" *ngIf="!torrent.autoUnpack" title="Auto unpack"></i> <i class="fas fa-box-open" *ngIf="torrent.autoUnpack" title="Auto unpack"></i>
<i class="fas fa-trash" *ngIf="!torrent.autoDelete" title="Auto delete"></i> <i class="fas fa-trash" *ngIf="torrent.autoDelete" title="Auto delete"></i>
</td> </td>
<td> <td>
{{ torrent | status }} {{ torrent | status }}

View file

@ -5,12 +5,12 @@
<div class="table-container"> <div class="table-container">
<table class="table is-fullwidth is-hoverable"> <table class="table is-fullwidth is-hoverable">
<colgroup> <colgroup>
<col style="width: calc(65% - 550px)" /> <col style="width: calc(40% - 550px)" />
<col style="width: 15%" /> <col style="width: 15%" />
<col style="width: 150px" /> <col style="width: 150px" />
<col style="width: 150px" /> <col style="width: 150px" />
<col style="width: 150px" /> <col style="width: 150px" />
<col style="width: 20%" /> <col style="width: 40%" />
<col style="width: 100px" /> <col style="width: 100px" />
</colgroup> </colgroup>
<thead> <thead>
@ -32,12 +32,7 @@
(click)="selectTorrent(torrent)" (click)="selectTorrent(torrent)"
(delete)="showDeleteModal($event)" (delete)="showDeleteModal($event)"
></tr> ></tr>
<ng-container *ngIf="showFiles[torrent.torrentId] && torrent?.files.length === 0"> <ng-container *ngIf="showFiles[torrent.torrentId]">
<tr>
<td colspan="20">
<i class="fas fa-spinner fa-pulse"></i>
</td>
</tr>
<tr app-torrent-file [file]="file" *ngFor="let file of torrent.files"></tr> <tr app-torrent-file [file]="file" *ngFor="let file of torrent.files"></tr>
</ng-container> </ng-container>
</ng-container> </ng-container>

View file

@ -20,26 +20,27 @@ export class TorrentTableComponent implements OnInit, OnDestroy {
public deleteRdTorrent: boolean; public deleteRdTorrent: boolean;
public deleteLocalFiles: boolean; public deleteLocalFiles: boolean;
private timer: any;
constructor(private torrentService: TorrentService) {} constructor(private torrentService: TorrentService) {}
ngOnInit(): void { ngOnInit(): void {
this.timer = setInterval(() => { this.torrentService.getList().subscribe(
this.torrentService.getList().subscribe( (result) => {
(result) => { this.torrents = result;
this.torrents = result;
}, this.torrentService.connect();
(err) => {
this.error = err.error; this.torrentService.update$.subscribe((result2) => {
clearInterval(this.timer); this.torrents = result2;
} });
); },
}, 1000); (err) => {
this.error = err.error;
}
);
} }
ngOnDestroy(): void { ngOnDestroy(): void {
clearInterval(this.timer); this.torrentService.disconnect();
} }
public selectTorrent(torrent: Torrent): void { public selectTorrent(torrent: Torrent): void {

View file

@ -1,6 +1,7 @@
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import * as signalR from '@microsoft/signalr';
import { Observable, Subject } from 'rxjs';
import { Torrent } from './models/torrent.model'; import { Torrent } from './models/torrent.model';
@Injectable({ @Injectable({
@ -9,6 +10,23 @@ import { Torrent } from './models/torrent.model';
export class TorrentService { export class TorrentService {
constructor(private http: HttpClient) {} constructor(private http: HttpClient) {}
public update$: Subject<Torrent[]> = new Subject();
private connection: signalR.HubConnection;
public connect(): void {
this.connection = new signalR.HubConnectionBuilder().withUrl('/hub').withAutomaticReconnect().build();
this.connection.start().catch((err) => console.error(err));
this.connection.on('update', (torrents: Torrent[]) => {
this.update$.next(torrents);
});
}
public disconnect(): void {
this.connection?.stop();
}
public getList(): Observable<Torrent[]> { public getList(): Observable<Torrent[]> {
return this.http.get<Torrent[]>(`/Api/Torrents`); return this.http.get<Torrent[]>(`/Api/Torrents`);
} }

View file

@ -10,6 +10,7 @@ namespace RdtClient.Service
services.AddScoped<IAuthentication, Authentication>(); services.AddScoped<IAuthentication, Authentication>();
services.AddScoped<IDownloads, Downloads>(); services.AddScoped<IDownloads, Downloads>();
services.AddScoped<IQBittorrent, QBittorrent>(); services.AddScoped<IQBittorrent, QBittorrent>();
services.AddScoped<IRemoteService, RemoteService>();
services.AddScoped<ISettings, Settings>(); services.AddScoped<ISettings, Settings>();
services.AddScoped<ITorrents, Torrents>(); services.AddScoped<ITorrents, Torrents>();
services.AddScoped<ITorrentRunner, TorrentRunner>(); services.AddScoped<ITorrentRunner, TorrentRunner>();

View file

@ -0,0 +1,27 @@
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
namespace RdtClient.Service.Services
{
public class RdtHub : Hub
{
private static readonly ConcurrentDictionary<String, String> Users = new ConcurrentDictionary<String, String>();
public static Boolean HasConnections => Users.Any();
public override async Task OnConnectedAsync()
{
Users.TryAdd(Context.ConnectionId, Context.ConnectionId);
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception exception)
{
Users.TryRemove(Context.ConnectionId, out _);
await base.OnDisconnectedAsync(exception);
}
}
}

View file

@ -0,0 +1,41 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
namespace RdtClient.Service.Services
{
public interface IRemoteService
{
Task Update();
}
public class RemoteService : IRemoteService
{
private readonly IHubContext<RdtHub> _hub;
private readonly ITorrents _torrents;
public RemoteService(IHubContext<RdtHub> hub, ITorrents torrents)
{
_hub = hub;
_torrents = torrents;
}
public async Task Update()
{
var torrents = await _torrents.Get();
// Prevent infinite recursion when serializing
foreach (var file in torrents.SelectMany(torrent => torrent.Downloads))
{
file.Torrent = null;
}
await _hub.Clients.All.SendCoreAsync("update",
new Object[]
{
torrents
});
}
}
}

View file

@ -18,23 +18,25 @@ namespace RdtClient.Service.Services
public static readonly ConcurrentDictionary<Guid, DownloadClient> ActiveDownloadClients = new ConcurrentDictionary<Guid, DownloadClient>(); public static readonly ConcurrentDictionary<Guid, DownloadClient> ActiveDownloadClients = new ConcurrentDictionary<Guid, DownloadClient>();
public static readonly ConcurrentDictionary<Guid, UnpackClient> ActiveUnpackClients = new ConcurrentDictionary<Guid, UnpackClient>(); public static readonly ConcurrentDictionary<Guid, UnpackClient> ActiveUnpackClients = new ConcurrentDictionary<Guid, UnpackClient>();
private readonly IDownloads _downloads;
private readonly IRemoteService _remoteService;
private readonly ISettings _settings; private readonly ISettings _settings;
private readonly ITorrents _torrents; private readonly ITorrents _torrents;
private readonly IDownloads _downloads;
public TorrentRunner(ISettings settings, ITorrents torrents, IDownloads downloads) public TorrentRunner(ISettings settings, ITorrents torrents, IDownloads downloads, IRemoteService remoteService)
{ {
_settings = settings; _settings = settings;
_torrents = torrents; _torrents = torrents;
_downloads = downloads; _downloads = downloads;
_remoteService = remoteService;
} }
public async Task Initialize() public async Task Initialize()
{ {
// When starting up reset any pending downloads or unpackings so that they are restarted. // When starting up reset any pending downloads or unpackings so that they are restarted.
var torrents = await _torrents.Get(); var torrents = await _torrents.Get();
var downloads = torrents.SelectMany(m => m.Downloads) var downloads = torrents.SelectMany(m => m.Downloads)
.Where(m => m.DownloadQueued != null && m.DownloadStarted != null && m.DownloadFinished == null) .Where(m => m.DownloadQueued != null && m.DownloadStarted != null && m.DownloadFinished == null)
.OrderBy(m => m.DownloadQueued); .OrderBy(m => m.DownloadQueued);
@ -43,7 +45,7 @@ namespace RdtClient.Service.Services
{ {
await _downloads.UpdateDownloadStarted(download.DownloadId, null); await _downloads.UpdateDownloadStarted(download.DownloadId, null);
} }
var unpacks = torrents.SelectMany(m => m.Downloads) var unpacks = torrents.SelectMany(m => m.Downloads)
.Where(m => m.UnpackingQueued != null && m.UnpackingStarted != null && m.UnpackingFinished == null) .Where(m => m.UnpackingQueued != null && m.UnpackingStarted != null && m.UnpackingFinished == null)
.OrderBy(m => m.DownloadQueued); .OrderBy(m => m.DownloadQueued);
@ -53,7 +55,7 @@ namespace RdtClient.Service.Services
await _downloads.UpdateUnpackingStarted(download.DownloadId, null); await _downloads.UpdateUnpackingStarted(download.DownloadId, null);
} }
} }
public async Task Tick() public async Task Tick()
{ {
var settingApiKey = await _settings.GetString("RealDebridApiKey"); var settingApiKey = await _settings.GetString("RealDebridApiKey");
@ -62,7 +64,7 @@ namespace RdtClient.Service.Services
{ {
return; return;
} }
// Check if any torrents are finished downloading to the host, remove them from the active download list. // 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(); var completedActiveDownloads = ActiveDownloadClients.Where(m => m.Value.Finished).ToList();
@ -78,14 +80,14 @@ namespace RdtClient.Service.Services
{ {
await _downloads.UpdateDownloadFinished(downloadId, DateTimeOffset.UtcNow); await _downloads.UpdateDownloadFinished(downloadId, DateTimeOffset.UtcNow);
} }
ActiveDownloadClients.TryRemove(downloadId, out _); ActiveDownloadClients.TryRemove(downloadId, out _);
} }
} }
// Check if any torrents are finished unpacking, remove them from the active unpack list. // Check if any torrents are finished unpacking, remove them from the active unpack list.
var completedUnpacks = ActiveUnpackClients.Where(m => m.Value.Finished).ToList(); var completedUnpacks = ActiveUnpackClients.Where(m => m.Value.Finished).ToList();
if (completedUnpacks.Count > 0) if (completedUnpacks.Count > 0)
{ {
foreach (var (downloadId, unpackClient) in completedUnpacks) foreach (var (downloadId, unpackClient) in completedUnpacks)
@ -98,24 +100,30 @@ namespace RdtClient.Service.Services
{ {
await _downloads.UpdateUnpackingFinished(downloadId, DateTimeOffset.UtcNow); await _downloads.UpdateUnpackingFinished(downloadId, DateTimeOffset.UtcNow);
} }
ActiveUnpackClients.TryRemove(downloadId, out _); ActiveUnpackClients.TryRemove(downloadId, out _);
} }
} }
// Only poll RealDebrid every 30 seconds // Only poll RealDebrid every 5 when a hub is connected, otherwise ever 30 seconds
if (_nextUpdate < DateTime.UtcNow) if (_nextUpdate < DateTime.UtcNow)
{ {
_nextUpdate = DateTime.UtcNow.AddSeconds(5); var updateTime = 30;
if (RdtHub.HasConnections)
{
updateTime = 5;
}
_nextUpdate = DateTime.UtcNow.AddSeconds(updateTime);
await _torrents.Update(); await _torrents.Update();
} }
var torrents = await _torrents.Get(); var torrents = await _torrents.Get();
torrents = torrents.Where(m => m.Completed == null).ToList(); torrents = torrents.Where(m => m.Completed == null).ToList();
// Check if there are any downloads that are queued and can be started. // Check if there are any downloads that are queued and can be started.
var queuedDownloads = torrents.SelectMany(m => m.Downloads) var queuedDownloads = torrents.SelectMany(m => m.Downloads)
.Where(m => m.DownloadQueued != null && m.DownloadStarted == null) .Where(m => m.DownloadQueued != null && m.DownloadStarted == null)
@ -125,7 +133,7 @@ namespace RdtClient.Service.Services
{ {
await _torrents.Download(download.DownloadId); await _torrents.Download(download.DownloadId);
} }
// Check if there are any unpacks that are queued and can be started. // Check if there are any unpacks that are queued and can be started.
var queuedUnpacks = torrents.SelectMany(m => m.Downloads) var queuedUnpacks = torrents.SelectMany(m => m.Downloads)
.Where(m => m.UnpackingQueued != null && m.UnpackingStarted == null) .Where(m => m.UnpackingQueued != null && m.UnpackingStarted == null)
@ -167,22 +175,23 @@ namespace RdtClient.Service.Services
if (torrent.Downloads.Count == 0) if (torrent.Downloads.Count == 0)
{ {
await _torrents.Unrestrict(torrent.TorrentId); await _torrents.Unrestrict(torrent.TorrentId);
continue; continue;
} }
// If the torrent has any files that need starting to be downloaded, download them. // If the torrent has any files that need starting to be downloaded, download them.
var downloadsPending = torrent.Downloads var downloadsPending = torrent.Downloads
.Where(m => m.DownloadStarted == null && .Where(m => m.DownloadStarted == null &&
m.DownloadFinished == null) m.DownloadFinished == null)
.ToList(); .ToList();
if (downloadsPending.Count > 0) if (downloadsPending.Count > 0)
{ {
foreach (var download in downloadsPending) foreach (var download in downloadsPending)
{ {
await _torrents.Download(download.DownloadId); await _torrents.Download(download.DownloadId);
} }
continue; continue;
} }
} }
@ -191,22 +200,22 @@ namespace RdtClient.Service.Services
{ {
// If all files are finished downloading, move to the unpacking step. // If all files are finished downloading, move to the unpacking step.
var unpackingPending = torrent.Downloads var unpackingPending = torrent.Downloads
.Where(m => m.DownloadFinished != null && .Where(m => m.DownloadFinished != null &&
m.UnpackingStarted == null && m.UnpackingStarted == null &&
m.UnpackingFinished == null) m.UnpackingFinished == null)
.ToList(); .ToList();
if (unpackingPending.Count > 0) if (unpackingPending.Count > 0)
{ {
foreach (var download in unpackingPending) foreach (var download in unpackingPending)
{ {
await _torrents.Unpack(download.DownloadId); await _torrents.Unpack(download.DownloadId);
} }
continue; continue;
} }
} }
// Check if torrent is complete // Check if torrent is complete
if (torrent.Downloads.Count > 0) if (torrent.Downloads.Count > 0)
{ {
@ -224,6 +233,8 @@ namespace RdtClient.Service.Services
} }
} }
} }
await _remoteService.Update();
} }
} }
} }

View file

@ -169,7 +169,10 @@ namespace RdtClient.Service.Services
var torrentPath = Path.Combine(settingDownloadFolder, torrent.RdName); var torrentPath = Path.Combine(settingDownloadFolder, torrent.RdName);
Directory.Delete(torrentPath, true); if (Directory.Exists(torrentPath))
{
Directory.Delete(torrentPath, true);
}
} }
if (deleteData) if (deleteData)

View file

@ -12,6 +12,7 @@ using Microsoft.Extensions.Logging;
using RdtClient.Data.Data; using RdtClient.Data.Data;
using RdtClient.Data.Models.Internal; using RdtClient.Data.Models.Internal;
using RdtClient.Service.Middleware; using RdtClient.Service.Middleware;
using RdtClient.Service.Services;
namespace RdtClient.Web namespace RdtClient.Web
{ {
@ -39,6 +40,11 @@ namespace RdtClient.Web
services.AddSpaStaticFiles(configuration => { configuration.RootPath = "wwwroot"; }); services.AddSpaStaticFiles(configuration => { configuration.RootPath = "wwwroot"; });
services.AddSignalR(options =>
{
options.EnableDetailedErrors = true;
});
services.AddHttpContextAccessor(); services.AddHttpContextAccessor();
services.AddCors(options => services.AddCors(options =>
@ -112,7 +118,11 @@ namespace RdtClient.Web
app.UseAuthorization(); app.UseAuthorization();
app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); app.UseEndpoints(endpoints =>
{
endpoints.MapHub<RdtHub>("/hub");
endpoints.MapControllers();
});
app.MapWhen(x => !x.Request.Path.Value.StartsWith("/api"), builder => app.MapWhen(x => !x.Request.Path.Value.StartsWith("/api"), builder =>
{ {