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:
Roger Far 2021-03-13 08:04:03 -07:00
parent 8b85521839
commit 3e1b30ad1a
13 changed files with 650 additions and 118 deletions

View file

@ -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">

View file

@ -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);
}
}

View file

@ -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>

View file

@ -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;
}
);
}
}

View file

@ -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,
});
}
}

View file

@ -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);

View 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
}
}
}

View file

@ -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");
}
}
}

View file

@ -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");

View file

@ -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; }

View file

@ -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

View file

@ -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);

View file

@ -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; }