Add a retry button in the interface.
Fixed issue with some downloads not processed. Fixed issue with deletion and files in use.
This commit is contained in:
parent
8b85521839
commit
3e1b30ad1a
13 changed files with 650 additions and 118 deletions
|
|
@ -15,18 +15,10 @@
|
|||
{{ torrent | status }}
|
||||
</td>
|
||||
<td>
|
||||
<span
|
||||
class="icon download-icon"
|
||||
(click)="download($event)"
|
||||
title="Download to disk"
|
||||
*ngIf="!loading && canDownload()"
|
||||
>
|
||||
<i class="fas fa-download"></i>
|
||||
<span class="icon sync-icon" (click)="retryClick($event)" title="Retry" *ngIf="!loading">
|
||||
<i class="fas fa-sync"></i>
|
||||
</span>
|
||||
<span class="icon download-icon" (click)="unpack($event)" title="Unpack" *ngIf="!loading && canUnpack()">
|
||||
<i class="fas fa-box-open"></i>
|
||||
</span>
|
||||
<span class="icon delete-icon" (click)="delete1($event)" title="Delete torrent" *ngIf="!loading">
|
||||
<span class="icon delete-icon" (click)="deleteClick($event)" title="Delete torrent" *ngIf="!loading">
|
||||
<i class="fas fa-times"></i>
|
||||
</span>
|
||||
<span class="icon loading-icon" *ngIf="loading">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { RealDebridStatus, Torrent } from 'src/app/models/torrent.model';
|
||||
import { TorrentService } from 'src/app/torrent.service';
|
||||
import { Torrent } from 'src/app/models/torrent.model';
|
||||
|
||||
@Component({
|
||||
selector: '[app-torrent-row]',
|
||||
|
|
@ -14,55 +13,22 @@ export class TorrentRowComponent implements OnInit {
|
|||
@Output('delete')
|
||||
public delete = new EventEmitter();
|
||||
|
||||
@Output('retry')
|
||||
public retry = new EventEmitter();
|
||||
|
||||
public loading = false;
|
||||
|
||||
constructor(private torrentService: TorrentService) {}
|
||||
constructor() {}
|
||||
|
||||
ngOnInit(): void {}
|
||||
|
||||
public download(event: Event): void {
|
||||
event.stopPropagation();
|
||||
|
||||
this.loading = true;
|
||||
this.torrentService.download(this.torrent.torrentId).subscribe(
|
||||
() => {
|
||||
this.loading = false;
|
||||
},
|
||||
(err) => {
|
||||
this.loading = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public unpack(event: Event): void {
|
||||
event.stopPropagation();
|
||||
|
||||
this.loading = true;
|
||||
this.torrentService.unpack(this.torrent.torrentId).subscribe(
|
||||
() => {
|
||||
this.loading = false;
|
||||
},
|
||||
(err) => {
|
||||
this.loading = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public delete1(event: Event): void {
|
||||
public deleteClick(event: Event): void {
|
||||
event.stopPropagation();
|
||||
this.delete.emit(this.torrent.torrentId);
|
||||
}
|
||||
|
||||
public canDownload(): boolean {
|
||||
return (
|
||||
(this.torrent.rdStatus === RealDebridStatus.Finished && this.torrent.downloads.length === 0) ||
|
||||
(this.torrent.downloads.length > 0 && this.torrent.downloads.any((m) => m.error != null))
|
||||
);
|
||||
}
|
||||
|
||||
public canUnpack(): boolean {
|
||||
const downloadsDone = this.torrent.downloads.any((m) => m.downloadFinished != null);
|
||||
const downloadsUnpacked = this.torrent.downloads.any((m) => m.unpackingQueued != null);
|
||||
return downloadsDone && !downloadsUnpacked;
|
||||
public retryClick(event: Event): void {
|
||||
event.stopPropagation();
|
||||
this.retry.emit(this.torrent.torrentId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
</div>
|
||||
<div class="table-container">
|
||||
<table class="table is-fullwidth is-hoverable">
|
||||
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
|
|
@ -23,6 +22,7 @@
|
|||
[torrent]="torrent"
|
||||
(click)="selectTorrent(torrent)"
|
||||
(delete)="showDeleteModal($event)"
|
||||
(retry)="showRetryModal($event)"
|
||||
></tr>
|
||||
<ng-container *ngIf="showFiles[torrent.torrentId]">
|
||||
<tr class="separator">
|
||||
|
|
@ -88,3 +88,45 @@
|
|||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal" [class.is-active]="isRetryModalActive">
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-card">
|
||||
<header class="modal-card-head">
|
||||
<p class="modal-card-title">Retry torrent</p>
|
||||
<button class="delete" aria-label="close" (click)="retryCancel()"></button>
|
||||
</header>
|
||||
<section class="modal-card-body">
|
||||
<div class="field">
|
||||
<label class="label">Retry</label>
|
||||
<div class="control">
|
||||
<label class="radio">
|
||||
<input type="radio" name="retry" [value]="0" [(ngModel)]="retry" />
|
||||
Retry on Real-Debrid
|
||||
</label>
|
||||
<br />
|
||||
<label class="radio">
|
||||
<input type="radio" name="retry" [value]="1" [(ngModel)]="retry" />
|
||||
Retry downloading locally
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="notification is-danger is-light" *ngIf="retryError?.length > 0">
|
||||
Error retrying torrent: {{ retryError }}
|
||||
</div>
|
||||
</section>
|
||||
<footer class="modal-card-foot">
|
||||
<button
|
||||
class="button is-success"
|
||||
(click)="retryOk()"
|
||||
[disabled]="retrying"
|
||||
[ngClass]="{ 'is-loading': retrying }"
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
<button class="button" (click)="retryCancel()" [disabled]="retrying" [ngClass]="{ 'is-loading': retrying }">
|
||||
Cancel
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -20,6 +20,12 @@ export class TorrentTableComponent implements OnInit, OnDestroy {
|
|||
public deleteRdTorrent: boolean;
|
||||
public deleteLocalFiles: boolean;
|
||||
|
||||
public isRetryModalActive: boolean;
|
||||
public retryError: string;
|
||||
public retrying: boolean;
|
||||
public retryTorrentId: string;
|
||||
public retry: number;
|
||||
|
||||
constructor(private torrentService: TorrentService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
|
@ -52,6 +58,10 @@ export class TorrentTableComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
public showDeleteModal(torrentId: string): void {
|
||||
this.deleteData = false;
|
||||
this.deleteRdTorrent = false;
|
||||
this.deleteLocalFiles = false;
|
||||
|
||||
this.deleteTorrentId = torrentId;
|
||||
this.isDeleteModalActive = true;
|
||||
}
|
||||
|
|
@ -76,4 +86,30 @@ export class TorrentTableComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
);
|
||||
}
|
||||
|
||||
public showRetryModal(torrentId: string): void {
|
||||
this.retry = 0;
|
||||
|
||||
this.retryTorrentId = torrentId;
|
||||
this.isRetryModalActive = true;
|
||||
}
|
||||
|
||||
public retryCancel(): void {
|
||||
this.isRetryModalActive = false;
|
||||
}
|
||||
|
||||
public retryOk(): void {
|
||||
this.retrying = true;
|
||||
|
||||
this.torrentService.retry(this.retryTorrentId, this.retry).subscribe(
|
||||
() => {
|
||||
this.isRetryModalActive = false;
|
||||
this.retrying = false;
|
||||
},
|
||||
(err) => {
|
||||
this.retryError = err.error;
|
||||
this.retrying = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,14 +57,6 @@ export class TorrentService {
|
|||
return this.http.post<string[]>(`/Api/Torrents/CheckFiles`, formData);
|
||||
}
|
||||
|
||||
public download(torrentId: string): Observable<void> {
|
||||
return this.http.get<void>(`/Api/Torrents/Download/${torrentId}`);
|
||||
}
|
||||
|
||||
public unpack(torrentId: string): Observable<void> {
|
||||
return this.http.get<void>(`/Api/Torrents/Unpack/${torrentId}`);
|
||||
}
|
||||
|
||||
public delete(
|
||||
torrentId: string,
|
||||
deleteData: boolean,
|
||||
|
|
@ -77,4 +69,10 @@ export class TorrentService {
|
|||
deleteLocalFiles,
|
||||
});
|
||||
}
|
||||
|
||||
public retry(torrentId: string, retry: number): Observable<void> {
|
||||
return this.http.post<void>(`/Api/Torrents/Retry/${torrentId}`, {
|
||||
retry,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,10 +12,10 @@ namespace RdtClient.Data.Data
|
|||
Task<IList<Torrent>> Get();
|
||||
Task<Torrent> GetById(Guid torrentId);
|
||||
Task<Torrent> GetByHash(String hash);
|
||||
Task<Torrent> Add(String realDebridId, String hash, String category, Boolean autoDelete);
|
||||
Task<Torrent> Add(String realDebridId, String hash, String category, Boolean autoDelete, String fileOrMagnetContents, Boolean isFile);
|
||||
Task UpdateRdData(Torrent torrent);
|
||||
Task UpdateCategory(Guid torrentId, String category);
|
||||
Task UpdateComplete(Guid torrentId, DateTimeOffset datetime);
|
||||
Task UpdateComplete(Guid torrentId, DateTimeOffset? datetime);
|
||||
Task Delete(Guid torrentId);
|
||||
}
|
||||
|
||||
|
|
@ -78,7 +78,7 @@ namespace RdtClient.Data.Data
|
|||
return dbTorrent;
|
||||
}
|
||||
|
||||
public async Task<Torrent> Add(String realDebridId, String hash, String category, Boolean autoDelete)
|
||||
public async Task<Torrent> Add(String realDebridId, String hash, String category, Boolean autoDelete, String fileOrMagnetContents, Boolean isFile)
|
||||
{
|
||||
var torrent = new Torrent
|
||||
{
|
||||
|
|
@ -87,7 +87,9 @@ namespace RdtClient.Data.Data
|
|||
RdId = realDebridId,
|
||||
Hash = hash.ToLower(),
|
||||
Category = category,
|
||||
AutoDelete = autoDelete
|
||||
AutoDelete = autoDelete,
|
||||
FileOrMagnet = fileOrMagnetContents,
|
||||
IsFile = isFile
|
||||
};
|
||||
|
||||
await _dataContext.Torrents.AddAsync(torrent);
|
||||
|
|
@ -140,7 +142,7 @@ namespace RdtClient.Data.Data
|
|||
await _dataContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task UpdateComplete(Guid torrentId, DateTimeOffset datetime)
|
||||
public async Task UpdateComplete(Guid torrentId, DateTimeOffset? datetime)
|
||||
{
|
||||
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
|
||||
|
||||
|
|
|
|||
418
server/RdtClient.Data/Migrations/20210313145632_Torrents_Add_FileOrMagnet.Designer.cs
generated
Normal file
418
server/RdtClient.Data/Migrations/20210313145632_Torrents_Add_FileOrMagnet.Designer.cs
generated
Normal file
|
|
@ -0,0 +1,418 @@
|
|||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using RdtClient.Data.Data;
|
||||
|
||||
namespace RdtClient.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(DataContext))]
|
||||
[Migration("20210313145632_Torrents_Add_FileOrMagnet")]
|
||||
partial class Torrents_Add_FileOrMagnet
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "5.0.3");
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("RoleNameIndex");
|
||||
|
||||
b.ToTable("AspNetRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetRoleClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("AccessFailedCount")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.HasDatabaseName("EmailIndex");
|
||||
|
||||
b.HasIndex("NormalizedUserName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UserNameIndex");
|
||||
|
||||
b.ToTable("AspNetUsers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProviderKey")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ProviderDisplayName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserLogins");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetUserRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("AspNetUserTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RdtClient.Data.Models.Data.Download", b =>
|
||||
{
|
||||
b.Property<Guid>("DownloadId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
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<string>("Path")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
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");
|
||||
|
||||
b.ToTable("Downloads");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RdtClient.Data.Models.Data.Setting", b =>
|
||||
{
|
||||
b.Property<string>("SettingId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("SettingId");
|
||||
|
||||
b.ToTable("Settings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RdtClient.Data.Models.Data.Torrent", b =>
|
||||
{
|
||||
b.Property<Guid>("TorrentId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset>("Added")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("AutoDelete")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Category")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset?>("Completed")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("FileOrMagnet")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Hash")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsFile")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("RdAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTimeOffset?>("RdEnded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("RdFiles")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("RdHost")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("RdId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("RdName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("RdProgress")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long?>("RdSeeders")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("RdSize")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long?>("RdSpeed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<long>("RdSplit")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("RdStatus")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("RdStatusRaw")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("TorrentId");
|
||||
|
||||
b.ToTable("Torrents");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RdtClient.Data.Models.Data.Download", b =>
|
||||
{
|
||||
b.HasOne("RdtClient.Data.Models.Data.Torrent", "Torrent")
|
||||
.WithMany("Downloads")
|
||||
.HasForeignKey("TorrentId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Torrent");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RdtClient.Data.Models.Data.Torrent", b =>
|
||||
{
|
||||
b.Navigation("Downloads");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace RdtClient.Data.Migrations
|
||||
{
|
||||
public partial class Torrents_Add_FileOrMagnet : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "FileOrMagnet",
|
||||
table: "Torrents",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "IsFile",
|
||||
table: "Torrents",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "FileOrMagnet",
|
||||
table: "Torrents");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "IsFile",
|
||||
table: "Torrents");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -291,9 +291,15 @@ namespace RdtClient.Data.Migrations
|
|||
b.Property<DateTimeOffset?>("Completed")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("FileOrMagnet")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Hash")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsFile")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTimeOffset>("RdAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ namespace RdtClient.Data.Models.Data
|
|||
public DateTimeOffset? Completed { get; set; }
|
||||
|
||||
public Boolean AutoDelete { get; set; }
|
||||
|
||||
public String FileOrMagnet { get; set; }
|
||||
public Boolean IsFile { get; set; }
|
||||
|
||||
[InverseProperty("Torrent")]
|
||||
public IList<Download> Downloads { get; set; }
|
||||
|
|
|
|||
|
|
@ -335,7 +335,7 @@ namespace RdtClient.Service.Services
|
|||
}
|
||||
|
||||
// RealDebrid is waiting for file selection, select which files to download.
|
||||
if (torrent.RdStatus == RealDebridStatus.WaitingForFileSelection && torrent.Downloads.Count == 0)
|
||||
if (torrent.RdStatus == RealDebridStatus.WaitingForFileSelection || torrent.Downloads.Count == 0)
|
||||
{
|
||||
Log.Debug($"Torrent {torrent.RdId} selecting files");
|
||||
|
||||
|
|
@ -390,8 +390,6 @@ namespace RdtClient.Service.Services
|
|||
// RealDebrid finished downloading the torrent, process the file to host.
|
||||
if (torrent.RdStatus == RealDebridStatus.Finished)
|
||||
{
|
||||
Log.Debug($"Torrent {torrent.RdId} completed, download starting");
|
||||
|
||||
// If the torrent has any files that need starting to be downloaded, download them.
|
||||
var downloadsPending = torrent.Downloads
|
||||
.Where(m => m.Completed == null &&
|
||||
|
|
@ -413,11 +411,6 @@ namespace RdtClient.Service.Services
|
|||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (torrent.RdStatus == RealDebridStatus.Finished)
|
||||
{
|
||||
Log.Debug($"Torrent {torrent.RdId} completed, unpack starting");
|
||||
|
||||
// If all files are finished downloading, move to the unpacking step.
|
||||
var unpackingPending = torrent.Downloads
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ namespace RdtClient.Service.Services
|
|||
public interface ITorrents
|
||||
{
|
||||
Task<IList<Torrent>> Get();
|
||||
Task<Torrent> GetById(Guid torrentId);
|
||||
Task<Torrent> GetByHash(String hash);
|
||||
Task UpdateCategory(String hash, String category);
|
||||
Task UploadMagnet(String magnetLink, String category, Boolean autoDelete);
|
||||
|
|
@ -32,6 +31,7 @@ namespace RdtClient.Service.Services
|
|||
Task<Profile> GetProfile();
|
||||
Task UpdateComplete(Guid torrentId, DateTimeOffset datetime);
|
||||
Task Update();
|
||||
Task Retry(Guid id, Int32 retry);
|
||||
}
|
||||
|
||||
public class Torrents : ITorrents
|
||||
|
|
@ -141,7 +141,7 @@ namespace RdtClient.Service.Services
|
|||
|
||||
var rdTorrent = await GetRdNetClient().AddTorrentMagnetAsync(magnetLink);
|
||||
|
||||
await Add(rdTorrent.Id, magnet.InfoHash.ToHex(), category, autoDelete);
|
||||
await Add(rdTorrent.Id, magnet.InfoHash.ToHex(), category, autoDelete, magnetLink, false);
|
||||
}
|
||||
|
||||
public async Task UploadFile(Byte[] bytes, String category, Boolean autoDelete)
|
||||
|
|
@ -150,7 +150,9 @@ namespace RdtClient.Service.Services
|
|||
|
||||
var rdTorrent = await GetRdNetClient().AddTorrentFileAsync(bytes);
|
||||
|
||||
await Add(rdTorrent.Id, torrent.InfoHash.ToHex(), category, autoDelete);
|
||||
var fileAsBase64 = Convert.ToBase64String(bytes);
|
||||
|
||||
await Add(rdTorrent.Id, torrent.InfoHash.ToHex(), category, autoDelete, fileAsBase64, true);
|
||||
}
|
||||
|
||||
public async Task<List<String>> GetAvailableFiles(String hash)
|
||||
|
|
@ -197,17 +199,6 @@ namespace RdtClient.Service.Services
|
|||
}
|
||||
}
|
||||
|
||||
if (deleteLocalFiles)
|
||||
{
|
||||
var downloadPath = await DownloadPath(torrent);
|
||||
downloadPath = Path.Combine(downloadPath, torrent.RdName);
|
||||
|
||||
if (Directory.Exists(downloadPath))
|
||||
{
|
||||
Directory.Delete(downloadPath, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (deleteData)
|
||||
{
|
||||
await _downloads.DeleteForTorrent(torrent.TorrentId);
|
||||
|
|
@ -218,6 +209,37 @@ namespace RdtClient.Service.Services
|
|||
{
|
||||
await GetRdNetClient().DeleteTorrentAsync(torrent.RdId);
|
||||
}
|
||||
|
||||
if (deleteLocalFiles)
|
||||
{
|
||||
var downloadPath = await DownloadPath(torrent);
|
||||
downloadPath = Path.Combine(downloadPath, torrent.RdName);
|
||||
|
||||
if (Directory.Exists(downloadPath))
|
||||
{
|
||||
var retry = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.Delete(downloadPath, true);
|
||||
|
||||
break;
|
||||
}
|
||||
catch
|
||||
{
|
||||
retry++;
|
||||
if (retry >= 3)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -270,7 +292,7 @@ namespace RdtClient.Service.Services
|
|||
return profile;
|
||||
}
|
||||
|
||||
private async Task Add(String rdTorrentId, String infoHash, String category, Boolean autoDelete)
|
||||
private async Task Add(String rdTorrentId, String infoHash, String category, Boolean autoDelete, String fileOrMagnetContents, Boolean isFile)
|
||||
{
|
||||
var w = await SemaphoreSlim.WaitAsync(60000);
|
||||
if (!w)
|
||||
|
|
@ -287,7 +309,7 @@ namespace RdtClient.Service.Services
|
|||
return;
|
||||
}
|
||||
|
||||
var newTorrent = await _torrentData.Add(rdTorrentId, infoHash, category, autoDelete);
|
||||
var newTorrent = await _torrentData.Add(rdTorrentId, infoHash, category, autoDelete, fileOrMagnetContents, isFile);
|
||||
|
||||
var rdTorrent = await GetRdNetClient().GetTorrentInfoAsync(rdTorrentId);
|
||||
|
||||
|
|
@ -305,8 +327,6 @@ namespace RdtClient.Service.Services
|
|||
|
||||
if (!w)
|
||||
{
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -352,6 +372,43 @@ namespace RdtClient.Service.Services
|
|||
}
|
||||
}
|
||||
|
||||
public async Task Retry(Guid id, Int32 retry)
|
||||
{
|
||||
var torrent = await _torrentData.GetById(id);
|
||||
|
||||
if (retry == 0)
|
||||
{
|
||||
await Delete(id, true, true, true);
|
||||
|
||||
if (String.IsNullOrWhiteSpace(torrent.FileOrMagnet))
|
||||
{
|
||||
throw new Exception($"Cannot re-add this torrent, original magnet or file not found");
|
||||
}
|
||||
|
||||
if (torrent.IsFile)
|
||||
{
|
||||
var bytes = Convert.FromBase64String(torrent.FileOrMagnet);
|
||||
|
||||
await UploadFile(bytes, torrent.Category, torrent.AutoDelete);
|
||||
}
|
||||
else
|
||||
{
|
||||
await UploadMagnet(torrent.FileOrMagnet, torrent.Category, torrent.AutoDelete);
|
||||
}
|
||||
}
|
||||
else if (retry == 1)
|
||||
{
|
||||
await Delete(id, false, false, true);
|
||||
|
||||
await _torrentData.UpdateComplete(id, null);
|
||||
await _downloads.DeleteForTorrent(id);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Invalid retry option {retry}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateComplete(Guid torrentId, DateTimeOffset datetime)
|
||||
{
|
||||
await _torrentData.UpdateComplete(torrentId, datetime);
|
||||
|
|
|
|||
|
|
@ -132,32 +132,12 @@ namespace RdtClient.Web.Controllers
|
|||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("Download/{id}")]
|
||||
public async Task<ActionResult> Download(Guid id)
|
||||
{
|
||||
var torrent = await _torrents.GetById(id);
|
||||
|
||||
foreach (var link in torrent.Files.Where(m => m.Selected))
|
||||
{
|
||||
await _downloads.Add(id, link.Path);
|
||||
await _torrents.UnrestrictLink(id);
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("Unpack/{id}")]
|
||||
public async Task<ActionResult> Unpack(Guid id)
|
||||
[HttpPost]
|
||||
[Route("Retry/{id}")]
|
||||
public async Task<ActionResult> Retry(Guid id, [FromBody] TorrentControllerRetryRequest request)
|
||||
{
|
||||
var downloads = await _downloads.GetForTorrent(id);
|
||||
|
||||
foreach (var download in downloads)
|
||||
{
|
||||
await _torrents.Unpack(download.DownloadId);
|
||||
}
|
||||
await _torrents.Retry(id, request.Retry);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
|
@ -181,6 +161,11 @@ namespace RdtClient.Web.Controllers
|
|||
public Boolean DeleteLocalFiles { get; set; }
|
||||
}
|
||||
|
||||
public class TorrentControllerRetryRequest
|
||||
{
|
||||
public Int32 Retry { get; set; }
|
||||
}
|
||||
|
||||
public class TorrentControllerCheckFilesRequest
|
||||
{
|
||||
public String MagnetLink { get; set; }
|
||||
|
|
|
|||
Loading…
Reference in a new issue