Add retry count as a setting per torrent and global.

Add error status on the torrent itself too.
Improved torrent retrying.
This commit is contained in:
Roger Far 2021-10-30 10:25:53 -06:00
parent a0d000138c
commit b3dd856410
44 changed files with 2143 additions and 627 deletions

View file

@ -84,12 +84,43 @@
<div class="field">
<label class="label">Priority</label>
<div class="control">
<input class="input" type="number" step="1" [(ngModel)]="priority" />
<input class="input" type="number" step="1" [(ngModel)]="priority" />
</div>
<p class="help">
Set the priority for this torrent where 1 is the highest. When empty it will be assigned the lowest priority.
</p>
</div>
<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"
/>
</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"
/>
</div>
<p class="help">
When a single download has failed multiple times (see setting above) or when the torrent itself received an
error it will retry the full torrent this many times before marking it failed.
</p>
</div>
</div>
<div fxFlex>
<div class="field">

View file

@ -1,7 +1,7 @@
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { TorrentService } from 'src/app/torrent.service';
import { TorrentFileAvailability } from '../models/torrent.model';
import { Torrent, TorrentFileAvailability } from '../models/torrent.model';
@Component({
selector: 'app-add-new-torrent',
@ -21,6 +21,9 @@ export class AddNewTorrentComponent implements OnInit {
public downloadMinSize: number = 0;
public downloadRetryAttempts: number = 3;
public torrentRetryAttempts: number = 1;
public availableFiles: TorrentFileAvailability[] = [];
public downloadFiles: { [key: string]: boolean } = {};
public allSelected: boolean;
@ -91,46 +94,36 @@ export class AddNewTorrentComponent implements OnInit {
downloadManualFiles = selectedFiles.join(',');
}
const torrent = new Torrent();
torrent.category = this.category;
torrent.downloadAction = this.downloadAction;
torrent.finishedAction = this.finishedAction;
torrent.downloadMinSize = this.downloadMinSize;
torrent.downloadManualFiles = downloadManualFiles;
torrent.priority = this.priority;
torrent.torrentRetryAttempts = this.torrentRetryAttempts;
torrent.downloadRetryAttempts = this.downloadRetryAttempts;
if (this.magnetLink) {
this.torrentService
.uploadMagnet(
this.magnetLink,
this.category,
this.downloadAction,
this.finishedAction,
this.downloadMinSize,
downloadManualFiles,
this.priority
)
.subscribe(
() => {
this.router.navigate(['/']);
},
(err) => {
this.error = err.error;
this.saving = false;
}
);
this.torrentService.uploadMagnet(this.magnetLink, torrent).subscribe(
() => {
this.router.navigate(['/']);
},
(err) => {
this.error = err.error;
this.saving = false;
}
);
} else if (this.selectedFile) {
this.torrentService
.uploadFile(
this.selectedFile,
this.category,
this.downloadAction,
this.finishedAction,
this.downloadMinSize,
downloadManualFiles,
this.priority
)
.subscribe(
() => {
this.router.navigate(['/']);
},
(err) => {
this.error = err.error;
this.saving = false;
}
);
this.torrentService.uploadFile(this.selectedFile, torrent).subscribe(
() => {
this.router.navigate(['/']);
},
(err) => {
this.error = err.error;
this.saving = false;
}
);
} else {
this.error = 'No magnet or file uploaded';
this.saving = false;

View file

@ -17,7 +17,11 @@ export class Torrent {
public isFile: boolean;
public retryCount: number;
public downloadRetryAttempts: number;
public torrentRetryAttempts: number;
public priority: number;
public error: string;
public rdId: string;
public rdName: string;

View file

@ -204,6 +204,25 @@
</div>
</div>
</div>
<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" />
</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" />
</div>
<p class="help">
When a single download has failed multiple times (see setting above) or when the torrent itself received an error
it will retry the full torrent this many times before marking it failed.
</p>
</div>
</div>
<div *ngIf="activeTab === 3">

View file

@ -42,6 +42,9 @@ export class SettingsComponent implements OnInit {
public aria2cUrl: string;
public aria2cSecret: string;
public downloadRetryAttempts: number;
public torrentRetryAttempts: number;
constructor(private settingsService: SettingsService) {}
ngOnInit(): void {
@ -69,6 +72,8 @@ export class SettingsComponent implements OnInit {
this.settingProxyServer = this.getSetting(results, 'ProxyServer');
this.aria2cUrl = this.getSetting(results, 'Aria2cUrl');
this.aria2cSecret = this.getSetting(results, 'Aria2cSecret');
this.downloadRetryAttempts = parseInt(this.getSetting(results, 'DownloadRetryAttempts'), 10);
this.torrentRetryAttempts = parseInt(this.getSetting(results, 'TorrentRetryAttempts'), 10);
},
(err) => {
this.error = err.error;
@ -141,6 +146,14 @@ export class SettingsComponent implements OnInit {
settingId: 'Aria2cSecret',
value: this.aria2cSecret,
},
{
settingId: 'DownloadRetryAttempts',
value: (this.downloadRetryAttempts ?? 0).toString(),
},
{
settingId: 'TorrentRetryAttempts',
value: (this.torrentRetryAttempts ?? 0).toString(),
},
];
this.settingsService.update(settings).subscribe(

View file

@ -9,6 +9,11 @@ export class TorrentStatusPipe implements PipeTransform {
constructor(private pipe: FileSizePipe) {}
transform(torrent: Torrent): string {
if (torrent.error) {
return torrent.error;
}
if (torrent.downloads.length > 0) {
const errors = torrent.downloads.where((m) => m.error != null);

View file

@ -37,41 +37,17 @@ export class TorrentService {
return this.http.get<Torrent>(`/Api/Torrents/Get/${torrentId}`);
}
public uploadMagnet(
magnetLink: string,
category: string,
downloadAction: number,
finishedAction: number,
downloadMinSize: number,
downloadManualFiles: string,
priority: number
): Observable<void> {
public uploadMagnet(magnetLink: string, torrent: Torrent): Observable<void> {
return this.http.post<void>(`/Api/Torrents/UploadMagnet`, {
magnetLink,
category,
downloadAction,
finishedAction,
downloadMinSize,
downloadManualFiles,
priority,
torrent,
});
}
public uploadFile(
file: File,
category: string,
downloadAction: number,
finishedAction: number,
downloadMinSize: number,
downloadManualFiles: string,
priority: number
): Observable<void> {
public uploadFile(file: File, torrent: Torrent): Observable<void> {
const formData: FormData = new FormData();
formData.append('file', file);
formData.append(
'formData',
JSON.stringify({ category, downloadAction, finishedAction, downloadMinSize, downloadManualFiles, priority })
);
formData.append('formData', JSON.stringify(torrent));
return this.http.post<void>(`/Api/Torrents/UploadFile`, formData);
}

View file

@ -28,7 +28,7 @@
<button class="button is-primary" (click)="showRetryModal()">Retry Torrent</button>
</div>
<div class="control">
<button class="button is-light" (click)="showUpdateSettingsModal()">Change priority</button>
<button class="button is-light" (click)="showUpdateSettingsModal()">Change Settings</button>
</div>
</div>
<div class="field">
@ -37,7 +37,7 @@
</div>
<div class="field">
<label class="label">Retry count</label>
{{ torrent.retryCount }} / 2
{{ torrent.retryCount }} / {{ torrent.torrentRetryAttempts}}
</div>
<div class="field">
<label class="label">Hash</label>
@ -256,7 +256,7 @@
</div>
<div class="field">
<label class="label">Retry Count</label>
{{ download.retryCount }} / 3
{{ download.retryCount }} / {{ torrent.downloadRetryAttempts }}
</div>
</div>
<div fxFlex>
@ -470,6 +470,37 @@
Set the priority for this torrent where 1 is the highest. When empty it will be assigned the lowest priority.
</p>
</div>
<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)]="updateSettingsDownloadRetryAttempts"
/>
</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)]="updateSettingsTorrentRetryAttempts"
/>
</div>
<p class="help">
When a single download has failed multiple times (see setting above) or when the torrent itself received an
error it will retry the full torrent this many times before marking it failed.
</p>
</div>
</section>
<footer class="modal-card-foot">
<button

View file

@ -36,6 +36,8 @@ export class TorrentComponent implements OnInit {
public isUpdateSettingsModalActive: boolean;
public updateSettingsPriority: number;
public updateSettingsDownloadRetryAttempts: number;
public updateSettingsTorrentRetryAttempts: number;
public updating: boolean;
constructor(private activatedRoute: ActivatedRoute, private router: Router, private torrentService: TorrentService) {}
@ -170,6 +172,8 @@ export class TorrentComponent implements OnInit {
public showUpdateSettingsModal(): void {
this.updateSettingsPriority = this.torrent.priority;
this.updateSettingsDownloadRetryAttempts = this.torrent.downloadRetryAttempts;
this.updateSettingsTorrentRetryAttempts = this.torrent.torrentRetryAttempts;
this.isUpdateSettingsModalActive = true;
}
@ -182,6 +186,8 @@ export class TorrentComponent implements OnInit {
this.updating = true;
this.torrent.priority = this.updateSettingsPriority;
this.torrent.downloadRetryAttempts = this.updateSettingsDownloadRetryAttempts;
this.torrent.torrentRetryAttempts = this.updateSettingsTorrentRetryAttempts;
this.torrentService.update(this.torrent).subscribe(
() => {

View file

@ -147,6 +147,18 @@ namespace RdtClient.Data.Data
SettingId = "Aria2cSecret",
Type = "String",
Value = ""
},
new Setting
{
SettingId = "DownloadRetryAttempts",
Type = "Int32",
Value = "3"
},
new Setting
{
SettingId = "TorrentRetryAttempts",
Type = "Int32",
Value = "1"
}
};

View file

@ -64,6 +64,8 @@ namespace RdtClient.Data.Data
Categories = GetString("Categories"),
Aria2cUrl = GetString("Aria2cUrl"),
Aria2cSecret = GetString("Aria2cSecret"),
DownloadRetryAttempts = GetInt32("DownloadRetryAttempts"),
TorrentRetryAttempts = GetInt32("TorrentRetryAttempts")
};
}

View file

@ -4,7 +4,6 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using RdtClient.Data.Enums;
using RdtClient.Data.Models.Data;
namespace RdtClient.Data.Data
@ -12,7 +11,7 @@ namespace RdtClient.Data.Data
public class TorrentData
{
private static IList<Torrent> _torrentCache;
private static readonly SemaphoreSlim _torrentCacheLock = new(1);
private static readonly SemaphoreSlim TorrentCacheLock = new(1, 1);
private readonly DataContext _dataContext;
@ -23,7 +22,7 @@ namespace RdtClient.Data.Data
public async Task<IList<Torrent>> Get()
{
await _torrentCacheLock.WaitAsync();
await TorrentCacheLock.WaitAsync();
try
{
@ -36,7 +35,7 @@ namespace RdtClient.Data.Data
}
finally
{
_torrentCacheLock.Release();
TorrentCacheLock.Release();
}
}
@ -82,38 +81,35 @@ namespace RdtClient.Data.Data
public async Task<Torrent> Add(String realDebridId,
String hash,
String category,
TorrentDownloadAction downloadAction,
TorrentFinishedAction finishedAction,
Int32 downloadMinSize,
String downloadManualFiles,
String fileOrMagnetContents,
Boolean isFile,
Int32? priority)
Torrent torrent)
{
var torrent = new Torrent
var newTorrent = new Torrent
{
TorrentId = Guid.NewGuid(),
Added = DateTimeOffset.UtcNow,
RdId = realDebridId,
Hash = hash.ToLower(),
Category = category,
DownloadAction = downloadAction,
FinishedAction = finishedAction,
DownloadMinSize = downloadMinSize,
DownloadManualFiles = downloadManualFiles,
Category = torrent.Category,
DownloadAction = torrent.DownloadAction,
FinishedAction = torrent.FinishedAction,
DownloadMinSize = torrent.DownloadMinSize,
DownloadManualFiles = torrent.DownloadManualFiles,
FileOrMagnet = fileOrMagnetContents,
IsFile = isFile,
Priority = priority
Priority = torrent.Priority,
TorrentRetryAttempts = torrent.TorrentRetryAttempts,
DownloadRetryAttempts = torrent.DownloadRetryAttempts
};
await _dataContext.Torrents.AddAsync(torrent);
await _dataContext.Torrents.AddAsync(newTorrent);
await _dataContext.SaveChangesAsync();
await VoidCache();
return torrent;
return newTorrent;
}
public async Task UpdateRdData(Torrent torrent)
@ -157,6 +153,8 @@ namespace RdtClient.Data.Data
}
dbTorrent.Priority = torrent.Priority;
dbTorrent.DownloadRetryAttempts = torrent.DownloadRetryAttempts;
dbTorrent.TorrentRetryAttempts = torrent.TorrentRetryAttempts;
await _dataContext.SaveChangesAsync();
@ -179,7 +177,7 @@ namespace RdtClient.Data.Data
await VoidCache();
}
public async Task UpdateComplete(Guid torrentId, DateTimeOffset? datetime)
public async Task UpdateComplete(Guid torrentId, String error, DateTimeOffset? datetime)
{
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
@ -188,7 +186,28 @@ namespace RdtClient.Data.Data
return;
}
if (String.IsNullOrWhiteSpace(error))
{
var downloads = await _dataContext.Downloads.AsNoTracking().Where(m => m.TorrentId == torrentId).ToListAsync();
var downloadWithErrors = downloads.Where(m => !String.IsNullOrWhiteSpace(m.Error)).ToList();
if (downloads.Any())
{
error = $"{downloadWithErrors.Count}/{downloads.Count} downloads failed with errors";
}
}
if (!String.IsNullOrWhiteSpace(error))
{
if (dbTorrent.RetryCount < dbTorrent.TorrentRetryAttempts)
{
dbTorrent.RetryCount += 1;
dbTorrent.Retry = DateTime.UtcNow;
}
}
dbTorrent.Completed = datetime;
dbTorrent.Error = error;
await _dataContext.SaveChangesAsync();
@ -211,22 +230,6 @@ namespace RdtClient.Data.Data
await VoidCache();
}
public async Task UpdateRetryCount(Guid torrentId, Int32 retryCount)
{
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
if (dbTorrent == null)
{
return;
}
dbTorrent.RetryCount = retryCount;
await _dataContext.SaveChangesAsync();
await VoidCache();
}
public async Task UpdatePriority(Guid torrentId, Int32? priority)
{
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
@ -243,6 +246,39 @@ namespace RdtClient.Data.Data
await VoidCache();
}
public async Task UpdateRetry(Guid torrentId, DateTimeOffset? dateTime, Int32 retryCount)
{
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
if (dbTorrent == null)
{
return;
}
dbTorrent.RetryCount = retryCount;
dbTorrent.Retry = dateTime;
await _dataContext.SaveChangesAsync();
await VoidCache();
}
public async Task UpdateError(Guid torrentId, String error)
{
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
if (dbTorrent == null)
{
return;
}
dbTorrent.Error = error;
await _dataContext.SaveChangesAsync();
await VoidCache();
}
public async Task Delete(Guid torrentId)
{
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
@ -261,7 +297,7 @@ namespace RdtClient.Data.Data
public static async Task VoidCache()
{
await _torrentCacheLock.WaitAsync();
await TorrentCacheLock.WaitAsync();
try
{
@ -269,7 +305,7 @@ namespace RdtClient.Data.Data
}
finally
{
_torrentCacheLock.Release();
TorrentCacheLock.Release();
}
}
}

View file

@ -0,0 +1,445 @@
// <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("20211029131846_Torrents_Add_Retry")]
partial class Torrents_Add_Retry
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "5.0.11");
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<string>("RemoteId")
.HasColumnType("TEXT");
b.Property<int>("RetryCount")
.HasColumnType("INTEGER");
b.Property<Guid>("TorrentId")
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("UnpackingFinished")
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("UnpackingQueued")
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("UnpackingStarted")
.HasColumnType("TEXT");
b.HasKey("DownloadId");
b.HasIndex("TorrentId");
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<string>("Category")
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("Completed")
.HasColumnType("TEXT");
b.Property<int>("DownloadAction")
.HasColumnType("INTEGER");
b.Property<string>("DownloadManualFiles")
.HasColumnType("TEXT");
b.Property<int>("DownloadMinSize")
.HasColumnType("INTEGER");
b.Property<string>("FileOrMagnet")
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("FilesSelected")
.HasColumnType("TEXT");
b.Property<int>("FinishedAction")
.HasColumnType("INTEGER");
b.Property<string>("Hash")
.HasColumnType("TEXT");
b.Property<bool>("IsFile")
.HasColumnType("INTEGER");
b.Property<int?>("Priority")
.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.Property<DateTimeOffset?>("Retry")
.HasColumnType("TEXT");
b.Property<int>("RetryCount")
.HasColumnType("INTEGER");
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,24 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace RdtClient.Data.Migrations
{
public partial class Torrents_Add_Retry : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTimeOffset>(
name: "Retry",
table: "Torrents",
type: "TEXT",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Retry",
table: "Torrents");
}
}
}

View file

@ -0,0 +1,448 @@
// <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("20211029133429_Torrents_Error")]
partial class Torrents_Error
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "5.0.11");
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<string>("RemoteId")
.HasColumnType("TEXT");
b.Property<int>("RetryCount")
.HasColumnType("INTEGER");
b.Property<Guid>("TorrentId")
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("UnpackingFinished")
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("UnpackingQueued")
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("UnpackingStarted")
.HasColumnType("TEXT");
b.HasKey("DownloadId");
b.HasIndex("TorrentId");
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<string>("Category")
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("Completed")
.HasColumnType("TEXT");
b.Property<int>("DownloadAction")
.HasColumnType("INTEGER");
b.Property<string>("DownloadManualFiles")
.HasColumnType("TEXT");
b.Property<int>("DownloadMinSize")
.HasColumnType("INTEGER");
b.Property<string>("Error")
.HasColumnType("TEXT");
b.Property<string>("FileOrMagnet")
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("FilesSelected")
.HasColumnType("TEXT");
b.Property<int>("FinishedAction")
.HasColumnType("INTEGER");
b.Property<string>("Hash")
.HasColumnType("TEXT");
b.Property<bool>("IsFile")
.HasColumnType("INTEGER");
b.Property<int?>("Priority")
.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.Property<DateTimeOffset?>("Retry")
.HasColumnType("TEXT");
b.Property<int>("RetryCount")
.HasColumnType("INTEGER");
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,23 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace RdtClient.Data.Migrations
{
public partial class Torrents_Error : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Error",
table: "Torrents",
type: "TEXT",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Error",
table: "Torrents");
}
}
}

View file

@ -0,0 +1,454 @@
// <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("20211029142739_Torrents_Add_RetryAttempts")]
partial class Torrents_Add_RetryAttempts
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "5.0.11");
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<string>("RemoteId")
.HasColumnType("TEXT");
b.Property<int>("RetryCount")
.HasColumnType("INTEGER");
b.Property<Guid>("TorrentId")
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("UnpackingFinished")
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("UnpackingQueued")
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("UnpackingStarted")
.HasColumnType("TEXT");
b.HasKey("DownloadId");
b.HasIndex("TorrentId");
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<string>("Category")
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("Completed")
.HasColumnType("TEXT");
b.Property<int>("DownloadAction")
.HasColumnType("INTEGER");
b.Property<string>("DownloadManualFiles")
.HasColumnType("TEXT");
b.Property<int>("DownloadMinSize")
.HasColumnType("INTEGER");
b.Property<int>("DownloadRetryAttempts")
.HasColumnType("INTEGER");
b.Property<string>("Error")
.HasColumnType("TEXT");
b.Property<string>("FileOrMagnet")
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("FilesSelected")
.HasColumnType("TEXT");
b.Property<int>("FinishedAction")
.HasColumnType("INTEGER");
b.Property<string>("Hash")
.HasColumnType("TEXT");
b.Property<bool>("IsFile")
.HasColumnType("INTEGER");
b.Property<int?>("Priority")
.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.Property<DateTimeOffset?>("Retry")
.HasColumnType("TEXT");
b.Property<int>("RetryCount")
.HasColumnType("INTEGER");
b.Property<int>("TorrentRetryAttempts")
.HasColumnType("INTEGER");
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,35 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace RdtClient.Data.Migrations
{
public partial class Torrents_Add_RetryAttempts : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "DownloadRetryAttempts",
table: "Torrents",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.AddColumn<int>(
name: "TorrentRetryAttempts",
table: "Torrents",
type: "INTEGER",
nullable: false,
defaultValue: 0);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "DownloadRetryAttempts",
table: "Torrents");
migrationBuilder.DropColumn(
name: "TorrentRetryAttempts",
table: "Torrents");
}
}
}

View file

@ -14,7 +14,7 @@ namespace RdtClient.Data.Migrations
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "5.0.10");
.HasAnnotation("ProductVersion", "5.0.11");
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
@ -303,6 +303,12 @@ namespace RdtClient.Data.Migrations
b.Property<int>("DownloadMinSize")
.HasColumnType("INTEGER");
b.Property<int>("DownloadRetryAttempts")
.HasColumnType("INTEGER");
b.Property<string>("Error")
.HasColumnType("TEXT");
b.Property<string>("FileOrMagnet")
.HasColumnType("TEXT");
@ -360,9 +366,15 @@ namespace RdtClient.Data.Migrations
b.Property<string>("RdStatusRaw")
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("Retry")
.HasColumnType("TEXT");
b.Property<int>("RetryCount")
.HasColumnType("INTEGER");
b.Property<int>("TorrentRetryAttempts")
.HasColumnType("INTEGER");
b.HasKey("TorrentId");
b.ToTable("Torrents");

View file

@ -11,24 +11,19 @@ namespace RdtClient.Data.Models.Data
public Guid TorrentId { get; set; }
public String Path { get; set; }
[ForeignKey("TorrentId")]
public Torrent Torrent { get; set; }
public String Path { get; set; }
public String Link { get; set; }
public DateTimeOffset Added { get; set; }
public DateTimeOffset? DownloadQueued { get; set; }
public DateTimeOffset? DownloadStarted { get; set; }
public DateTimeOffset? DownloadFinished { get; set; }
public DateTimeOffset? UnpackingQueued { get; set; }
public DateTimeOffset? UnpackingStarted { get; set; }
public DateTimeOffset? UnpackingFinished { get; set; }
public DateTimeOffset? Completed { get; set; }
public Int32 RetryCount { get; set; }
@ -37,9 +32,6 @@ namespace RdtClient.Data.Models.Data
public String RemoteId { get; set; }
[ForeignKey("TorrentId")]
public Torrent Torrent { get; set; }
[NotMapped]
public Int64 BytesTotal { get; set; }

View file

@ -3,8 +3,8 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Newtonsoft.Json;
using RDNET;
using RdtClient.Data.Enums;
using RdtClient.Data.Models.TorrentClient;
namespace RdtClient.Data.Models.Data
{
@ -25,12 +25,17 @@ namespace RdtClient.Data.Models.Data
public DateTimeOffset Added { get; set; }
public DateTimeOffset? FilesSelected { get; set; }
public DateTimeOffset? Completed { get; set; }
public DateTimeOffset? Retry { get; set; }
public String FileOrMagnet { get; set; }
public Boolean IsFile { get; set; }
public Int32? Priority { get; set; }
public Int32 RetryCount { get; set; }
public Int32 DownloadRetryAttempts { get; set; }
public Int32 TorrentRetryAttempts { get; set; }
public String Error { get; set; }
[InverseProperty("Torrent")]
public IList<Download> Downloads { get; set; }
@ -50,22 +55,22 @@ namespace RdtClient.Data.Models.Data
public String RdFiles { get; set; }
[NotMapped]
public IList<TorrentFile> Files
public IList<TorrentClientFile> Files
{
get
{
if (String.IsNullOrWhiteSpace(RdFiles))
{
return new List<TorrentFile>();
return new List<TorrentClientFile>();
}
try
{
return JsonConvert.DeserializeObject<List<TorrentFile>>(RdFiles);
return JsonConvert.DeserializeObject<List<TorrentClientFile>>(RdFiles);
}
catch
{
return new List<TorrentFile>();
return new List<TorrentClientFile>();
}
}
}

View file

@ -20,5 +20,7 @@ namespace RdtClient.Data.Models.Internal
public String Categories { get; set; }
public String Aria2cUrl { get; set; }
public String Aria2cSecret { get; set; }
public Int32 DownloadRetryAttempts { get; set; }
public Int32 TorrentRetryAttempts { get; set; }
}
}

View file

@ -1,6 +1,6 @@
using System;
namespace RdtClient.Service.Models
namespace RdtClient.Data.Models.Internal
{
public class Profile
{

View file

@ -1,7 +1,7 @@
using System;
using Newtonsoft.Json;
namespace RdtClient.Service.Models.QBittorrent
namespace RdtClient.Data.Models.QBittorrent
{
public class AppBuildInfo
{

View file

@ -1,7 +1,7 @@
using System;
using Newtonsoft.Json;
namespace RdtClient.Service.Models.QBittorrent
namespace RdtClient.Data.Models.QBittorrent
{
namespace QuickType
{

View file

@ -1,7 +1,7 @@
using System;
using Newtonsoft.Json;
namespace RdtClient.Service.Models.QBittorrent
namespace RdtClient.Data.Models.QBittorrent
{
public class TorrentCategory
{

View file

@ -1,7 +1,7 @@
using System;
using Newtonsoft.Json;
namespace RdtClient.Service.Models.QBittorrent
namespace RdtClient.Data.Models.QBittorrent
{
public class TorrentFileItem
{

View file

@ -1,7 +1,7 @@
using System;
using Newtonsoft.Json;
namespace RdtClient.Service.Models.QBittorrent
namespace RdtClient.Data.Models.QBittorrent
{
namespace QuickType
{

View file

@ -1,7 +1,7 @@
using System;
using Newtonsoft.Json;
namespace RdtClient.Service.Models.QBittorrent
namespace RdtClient.Data.Models.QBittorrent
{
public class TorrentProperties
{

View file

@ -1,6 +1,6 @@
using System;
namespace RdtClient.Service.Models.TorrentClient
namespace RdtClient.Data.Models.TorrentClient
{
public class TorrentClientAvailableFile
{

View file

@ -0,0 +1,12 @@
using System;
namespace RdtClient.Data.Models.TorrentClient
{
public class TorrentClientFile
{
public Int64 Id { get; set; }
public String Path { get; set; }
public Int64 Bytes { get; set; }
public Boolean Selected { get; set; }
}
}

View file

@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
namespace RdtClient.Service.Models.TorrentClient
namespace RdtClient.Data.Models.TorrentClient
{
public class TorrentClientTorrent
{
@ -16,18 +16,10 @@ namespace RdtClient.Service.Models.TorrentClient
public Int64 Progress { get; set; }
public String Status { get; set; }
public DateTimeOffset Added { get; set; }
public List<TorrentClientTorrentFile> Files { get; set; }
public List<TorrentClientFile> Files { get; set; }
public List<String> Links { get; set; }
public DateTimeOffset? Ended { get; set; }
public Int64? Speed { get; set; }
public Int64? Seeders { get; set; }
}
public class TorrentClientTorrentFile
{
public Int64 Id { get; set; }
public String Path { get; set; }
public Int64 Bytes { get; set; }
public Boolean Selected { get; set; }
}
}

View file

@ -1,6 +1,6 @@
using System;
namespace RdtClient.Service.Models.TorrentClient
namespace RdtClient.Data.Models.TorrentClient
{
public class TorrentClientUser
{

View file

@ -14,14 +14,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="RD.NET" Version="2.0.0" />
<PackageReference Include="Serilog" Version="2.10.0" />
</ItemGroup>
<ItemGroup>
<Reference Include="RDNET">
<HintPath>..\libs\RDNET.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View file

@ -6,9 +6,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AllDebrid.NET" Version="1.0.1" />
<PackageReference Include="Aria2.NET" Version="1.0.4" />
<PackageReference Include="Downloader" Version="2.2.9" />
<PackageReference Include="MonoTorrent" Version="2.0.0" />
<PackageReference Include="MonoTorrent" Version="2.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="RD.NET" Version="2.1.0" />
<PackageReference Include="Serilog" Version="2.10.0" />

View file

@ -4,8 +4,9 @@ using System.IO;
using System.Linq;
using System.Threading.Tasks;
using RdtClient.Data.Enums;
using RdtClient.Service.Models.QBittorrent;
using RdtClient.Service.Models.QBittorrent.QuickType;
using RdtClient.Data.Models.Data;
using RdtClient.Data.Models.QBittorrent;
using RdtClient.Data.Models.QBittorrent.QuickType;
namespace RdtClient.Service.Services
{
@ -293,7 +294,15 @@ namespace RdtClient.Service.Services
Upspeed = speed
};
if (torrent.Completed.HasValue)
if (torrent.Retry != null)
{
result.State = "downloading";
}
else if (!String.IsNullOrWhiteSpace(torrent.Error))
{
result.State = torrent.Error;
}
else if (torrent.Completed.HasValue)
{
var allDownloadsComplete = torrent.Downloads.All(m => m.Completed.HasValue);
var hasDownloadsWithErrors = torrent.Downloads.Any(m => m.Error != null);
@ -419,16 +428,34 @@ namespace RdtClient.Service.Services
public async Task TorrentsAddMagnet(String magnetLink, String category, Int32? priority)
{
var downloadAction = Settings.Get.OnlyDownloadAvailableFiles == 1 ? TorrentDownloadAction.DownloadAvailableFiles : TorrentDownloadAction.DownloadAll;
var torrent = new Torrent
{
Category = category,
DownloadAction = Settings.Get.OnlyDownloadAvailableFiles == 1 ? TorrentDownloadAction.DownloadAvailableFiles : TorrentDownloadAction.DownloadAll,
FinishedAction = TorrentFinishedAction.None,
DownloadMinSize = Settings.Get.MinFileSize,
TorrentRetryAttempts = Settings.Get.TorrentRetryAttempts,
DownloadRetryAttempts = Settings.Get.DownloadRetryAttempts,
Priority = priority
};
await _torrents.UploadMagnet(magnetLink, category, downloadAction, TorrentFinishedAction.None, Settings.Get.MinFileSize, null, priority);
await _torrents.UploadMagnet(magnetLink, torrent);
}
public async Task TorrentsAddFile(Byte[] fileBytes, String category, Int32? priority)
{
var downloadAction = Settings.Get.OnlyDownloadAvailableFiles == 1 ? TorrentDownloadAction.DownloadAvailableFiles : TorrentDownloadAction.DownloadAll;
var torrent = new Torrent
{
Category = category,
DownloadAction = Settings.Get.OnlyDownloadAvailableFiles == 1 ? TorrentDownloadAction.DownloadAvailableFiles : TorrentDownloadAction.DownloadAll,
FinishedAction = TorrentFinishedAction.None,
DownloadMinSize = Settings.Get.MinFileSize,
TorrentRetryAttempts = Settings.Get.TorrentRetryAttempts,
DownloadRetryAttempts = Settings.Get.DownloadRetryAttempts,
Priority = priority
};
await _torrents.UploadFile(fileBytes, category, downloadAction, TorrentFinishedAction.None, Settings.Get.MinFileSize, null, priority);
await _torrents.UploadFile(fileBytes, torrent);
}
public async Task TorrentsSetCategory(String hash, String category)

View file

@ -3,8 +3,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using RDNET;
using RdtClient.Service.Models.TorrentClient;
using AllDebridNET;
using RdtClient.Data.Models.TorrentClient;
namespace RdtClient.Service.Services.TorrentClients
{
@ -17,139 +17,110 @@ namespace RdtClient.Service.Services.TorrentClients
_httpClientFactory = httpClientFactory;
}
private RdNetClient GetRdNetClient()
private AllDebridNETClient GetClient()
{
var apiKey = Settings.Get.RealDebridApiKey;
if (String.IsNullOrWhiteSpace(apiKey))
{
throw new Exception("Real-Debrid API Key not set in the settings");
throw new Exception("All-Debrid API Key not set in the settings");
}
var httpClient = _httpClientFactory.CreateClient();
httpClient.Timeout = TimeSpan.FromSeconds(10);
var rdtNetClient = new RdNetClient(null, httpClient, 5);
rdtNetClient.UseApiAuthentication(apiKey);
var allDebridNetClient = new AllDebridNETClient("RealDebridClient", apiKey);
return rdtNetClient;
return allDebridNetClient;
}
private static TorrentClientTorrent Map(Torrent torrent)
private static TorrentClientTorrent Map(Magnet torrent)
{
return new TorrentClientTorrent
{
Id = torrent.Id,
Id = torrent.Id.ToString(),
Filename = torrent.Filename,
OriginalFilename = torrent.OriginalFilename,
OriginalFilename = torrent.Filename,
Hash = torrent.Hash,
Bytes = torrent.Bytes,
OriginalBytes = torrent.OriginalBytes,
Host = torrent.Host,
Split = torrent.Split,
Progress = torrent.Progress,
Bytes = torrent.Size,
OriginalBytes = torrent.Size,
Host = null,
Split = 0,
Progress = torrent.ProcessingPerc,
Status = torrent.Status,
Added = torrent.Added,
Files = (torrent.Files ?? new List<TorrentFile>()).Select(m => new TorrentClientTorrentFile
Added = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(torrent.UploadDate),
Files = (torrent.Links ?? new List<Link>()).Select(m => new TorrentClientFile
{
Path = m.Path,
Bytes = m.Bytes,
Id = m.Id,
Selected = m.Selected
Path = m.Filename,
Bytes = m.Size,
Id = 0,
Selected = true
}).ToList(),
Links = torrent.Links,
Ended = torrent.Ended,
Speed = torrent.Speed,
Seeders = torrent.Seeders,
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),
Speed = torrent.DownloadSpeed,
Seeders = torrent.Seeders
};
}
public async Task<IList<TorrentClientTorrent>> GetTorrents()
{
var page = 0;
var results = new List<Torrent>();
while (true)
{
var pagedResults = await GetRdNetClient().Torrents.GetAsync(page, 100);
results.AddRange(pagedResults);
if (pagedResults.Count == 0)
{
break;
}
page += 100;
}
var results = await GetClient().Magnet.StatusAllAsync();
return results.Select(Map).ToList();
}
public async Task<TorrentClientUser> GetUser()
{
var user = await GetRdNetClient().User.GetAsync();
var user = await GetClient().User.GetAsync();
return new TorrentClientUser
{
Username = user.Username,
Expiration = user.Expiration
Expiration = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(user.PremiumUntil)
};
}
public async Task<String> AddMagnet(String magnetLink)
{
var result = await GetRdNetClient().Torrents.AddMagnetAsync(magnetLink);
var result = await GetClient().Magnet.UploadMagnetAsync(magnetLink);
return result.Id;
return result.Id.ToString();
}
public async Task<String> AddFile(Byte[] bytes)
{
var result = await GetRdNetClient().Torrents.AddFileAsync(bytes);
var result = await GetClient().Magnet.UploadFileAsync(bytes);
return result.Id;
return result.Id.ToString();
}
public async Task<List<TorrentClientAvailableFile>> GetAvailableFiles(String hash)
public Task<List<TorrentClientAvailableFile>> GetAvailableFiles(String hash)
{
var result = await GetRdNetClient().Torrents.GetAvailableFiles(hash);
var files = result.SelectMany(m => m.Value).SelectMany(m => m.Value).SelectMany(m => m.Values);
var groups = files.GroupBy(m => $"{m.Filename}-{m.Filesize}");
var torrentClientAvailableFiles = groups.Select(m => new TorrentClientAvailableFile
{
Filename = m.First().Filename,
Filesize = m.First().Filesize
} ).ToList();
return torrentClientAvailableFiles;
return Task.FromResult(new List<TorrentClientAvailableFile>());
}
public async Task SelectFiles(String torrentId, IList<String> fileIds)
public Task SelectFiles(String torrentId, IList<String> fileIds)
{
await GetRdNetClient().Torrents.SelectFilesAsync(torrentId, fileIds.ToArray());
return Task.CompletedTask;
}
public async Task<TorrentClientTorrent> GetInfo(String torrentId)
{
var result = await GetRdNetClient().Torrents.GetInfoAsync(torrentId);
var result = await GetClient().Magnet.StatusAsync(torrentId);
return Map(result);
}
public async Task Delete(String torrentId)
{
await GetRdNetClient().Torrents.DeleteAsync(torrentId);
await GetClient().Magnet.DeleteAsync(torrentId);
}
public async Task<String> Unrestrict(String link)
{
var result = await GetRdNetClient().Unrestrict.LinkAsync(link);
var result = await GetClient().Links.DownloadLinkAsync(link);
return result.Download;
return result.Link;
}
}
}

View file

@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using RdtClient.Service.Models.TorrentClient;
using RdtClient.Data.Models.TorrentClient;
namespace RdtClient.Service.Services.TorrentClients
{

View file

@ -4,7 +4,7 @@ using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using RDNET;
using RdtClient.Service.Models.TorrentClient;
using RdtClient.Data.Models.TorrentClient;
namespace RdtClient.Service.Services.TorrentClients
{
@ -17,7 +17,7 @@ namespace RdtClient.Service.Services.TorrentClients
_httpClientFactory = httpClientFactory;
}
private RdNetClient GetRdNetClient()
private RdNetClient GetClient()
{
var apiKey = Settings.Get.RealDebridApiKey;
@ -50,7 +50,7 @@ namespace RdtClient.Service.Services.TorrentClients
Progress = torrent.Progress,
Status = torrent.Status,
Added = torrent.Added,
Files = (torrent.Files ?? new List<TorrentFile>()).Select(m => new TorrentClientTorrentFile
Files = (torrent.Files ?? new List<TorrentFile>()).Select(m => new TorrentClientFile
{
Path = m.Path,
Bytes = m.Bytes,
@ -71,7 +71,7 @@ namespace RdtClient.Service.Services.TorrentClients
while (true)
{
var pagedResults = await GetRdNetClient().Torrents.GetAsync(page, 100);
var pagedResults = await GetClient().Torrents.GetAsync(page, 100);
results.AddRange(pagedResults);
@ -88,7 +88,7 @@ namespace RdtClient.Service.Services.TorrentClients
public async Task<TorrentClientUser> GetUser()
{
var user = await GetRdNetClient().User.GetAsync();
var user = await GetClient().User.GetAsync();
return new TorrentClientUser
{
@ -99,21 +99,21 @@ namespace RdtClient.Service.Services.TorrentClients
public async Task<String> AddMagnet(String magnetLink)
{
var result = await GetRdNetClient().Torrents.AddMagnetAsync(magnetLink);
var result = await GetClient().Torrents.AddMagnetAsync(magnetLink);
return result.Id;
}
public async Task<String> AddFile(Byte[] bytes)
{
var result = await GetRdNetClient().Torrents.AddFileAsync(bytes);
var result = await GetClient().Torrents.AddFileAsync(bytes);
return result.Id;
}
public async Task<List<TorrentClientAvailableFile>> GetAvailableFiles(String hash)
{
var result = await GetRdNetClient().Torrents.GetAvailableFiles(hash);
var result = await GetClient().Torrents.GetAvailableFiles(hash);
var files = result.SelectMany(m => m.Value).SelectMany(m => m.Value).SelectMany(m => m.Values);
@ -130,24 +130,24 @@ namespace RdtClient.Service.Services.TorrentClients
public async Task SelectFiles(String torrentId, IList<String> fileIds)
{
await GetRdNetClient().Torrents.SelectFilesAsync(torrentId, fileIds.ToArray());
await GetClient().Torrents.SelectFilesAsync(torrentId, fileIds.ToArray());
}
public async Task<TorrentClientTorrent> GetInfo(String torrentId)
{
var result = await GetRdNetClient().Torrents.GetInfoAsync(torrentId);
var result = await GetClient().Torrents.GetInfoAsync(torrentId);
return Map(result);
}
public async Task Delete(String torrentId)
{
await GetRdNetClient().Torrents.DeleteAsync(torrentId);
await GetClient().Torrents.DeleteAsync(torrentId);
}
public async Task<String> Unrestrict(String link)
{
var result = await GetRdNetClient().Unrestrict.LinkAsync(link);
var result = await GetClient().Unrestrict.LinkAsync(link);
return result.Download;
}

View file

@ -4,11 +4,9 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using Aria2NET;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using RdtClient.Data.Enums;
@ -21,27 +19,20 @@ namespace RdtClient.Service.Services
{
public class TorrentRunner
{
private const Int32 DownloadRetryCount = 3;
private const Int32 TorrentRetryCount = 2;
private static DateTime _nextUpdate = DateTime.UtcNow;
public static readonly ConcurrentDictionary<Guid, DownloadClient> ActiveDownloadClients = new();
public static readonly ConcurrentDictionary<Guid, UnpackClient> ActiveUnpackClients = new();
public static readonly SemaphoreSlim TorrentResetLock = new(1, 1);
private readonly ILogger<TorrentRunner> _logger;
private readonly IServiceProvider _serviceProvider;
private readonly Torrents _torrents;
private readonly Downloads _downloads;
private readonly RemoteService _remoteService;
private readonly HttpClient _httpClient;
public TorrentRunner(ILogger<TorrentRunner> logger, IServiceProvider serviceProvider, Torrents torrents, Downloads downloads, RemoteService remoteService)
public TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Downloads downloads, RemoteService remoteService)
{
_logger = logger;
_serviceProvider = serviceProvider;
_torrents = torrents;
_downloads = downloads;
_remoteService = remoteService;
@ -174,38 +165,22 @@ namespace RdtClient.Service.Services
Log("Processing download", download, download.Torrent);
if (downloadClient.Error != null)
if (!String.IsNullOrWhiteSpace(downloadClient.Error))
{
// Retry the download if an error is encountered.
Log($"Download reported an error: {downloadClient.Error}", download, download.Torrent);
Log($"Download retry count {download.RetryCount}/{DownloadRetryCount}, torrent retry count {download.Torrent.RetryCount}/{TorrentRetryCount}", download, download.Torrent);
Log($"Download retry count {download.RetryCount}/{download.Torrent.DownloadRetryAttempts}, torrent retry count {download.Torrent.RetryCount}/{download.Torrent.TorrentRetryAttempts}", download, download.Torrent);
if (download.RetryCount < DownloadRetryCount)
if (download.RetryCount < download.Torrent.DownloadRetryAttempts)
{
Log($"Retrying download", download, download.Torrent);
await _downloads.Reset(downloadId);
await _downloads.UpdateRetryCount(downloadId, download.RetryCount + 1);
}
else if (download.Torrent.RetryCount < TorrentRetryCount)
{
Log($"Retrying torrent", download, download.Torrent);
_ = Task.Run(async () =>
{
using var blockScope = _serviceProvider.CreateScope();
var torrentsService = (Torrents) blockScope.ServiceProvider.GetService(typeof(Torrents));
await torrentsService.RetryTorrent(download.TorrentId, download.Torrent.RetryCount + 1);
});
// Force a delay and return the main loop to prevent concurrency issues.
await Task.Delay(10000);
return;
}
else
{
Log($"Not retrying", download, download.Torrent);
Log($"Not retrying download", download, download.Torrent);
await _downloads.UpdateError(downloadId, downloadClient.Error);
await _downloads.UpdateCompleted(downloadId, DateTimeOffset.UtcNow);
@ -268,6 +243,29 @@ namespace RdtClient.Service.Services
var torrents = await _torrents.Get();
// Process torrent retries
foreach (var torrent in torrents.Where(m => m.Retry != null))
{
try
{
_logger.LogDebug($"Retrying torrent {torrent.RetryCount}/{torrent.TorrentRetryAttempts}", torrent);
if (torrent.RetryCount > torrent.TorrentRetryAttempts)
{
await _torrents.UpdateRetry(torrent.TorrentId, null, torrent.RetryCount);
_logger.LogDebug($"Torrent reach max retry count", torrent);
continue;
}
await _torrents.RetryTorrent(torrent.TorrentId, torrent.RetryCount);
}
catch (Exception ex)
{
await _torrents.UpdateRetry(torrent.TorrentId, null, torrent.RetryCount);
await _torrents.UpdateError(torrent.TorrentId, ex.Message);
}
}
torrents = torrents.Where(m => m.Completed == null).ToList();
// Only poll Real-Debrid every second when a hub is connected, otherwise every 30 seconds
@ -308,305 +306,301 @@ namespace RdtClient.Service.Services
foreach (var torrent in torrents)
{
// Check if there are any downloads that are queued and can be started.
var queuedDownloads = torrent.Downloads
.Where(m => m.Completed == null && m.DownloadQueued != null && m.DownloadStarted == null && m.Error == null)
.OrderBy(m => m.DownloadQueued)
.ToList();
foreach (var download in queuedDownloads)
try
{
Log($"Processing to download", download, torrent);
// Check if there are any downloads that are queued and can be started.
var queuedDownloads = torrent.Downloads
.Where(m => m.Completed == null && m.DownloadQueued != null && m.DownloadStarted == null && m.Error == null)
.OrderBy(m => m.DownloadQueued)
.ToList();
if (ActiveDownloadClients.Count >= settingDownloadLimit)
foreach (var download in queuedDownloads)
{
Log($"Not starting download because there are already the max number of downloads active", download, torrent);
Log($"Processing to download", download, torrent);
continue;
}
if (ActiveDownloadClients.ContainsKey(download.DownloadId))
{
Log($"Not starting download because this download is already active", download, torrent);
continue;
}
try
{
Log($"Unrestricting links", download, torrent);
var downloadLink = await _torrents.UnrestrictLink(download.DownloadId);
download.Link = downloadLink;
}
catch (Exception ex)
{
_logger.LogError(ex, $"Cannot unrestrict link: {ex.Message}");
await _downloads.UpdateError(download.DownloadId, ex.Message);
await _downloads.UpdateCompleted(download.DownloadId, DateTimeOffset.UtcNow);
download.Error = ex.Message;
download.Completed = DateTimeOffset.UtcNow;
continue;
}
Log($"Marking download as started", download, torrent);
download.DownloadStarted = DateTime.UtcNow;
await _downloads.UpdateDownloadStarted(download.DownloadId, download.DownloadStarted);
var downloadPath = settingDownloadPath;
if (!String.IsNullOrWhiteSpace(torrent.Category))
{
downloadPath = Path.Combine(downloadPath, torrent.Category);
}
Log($"Setting download path to {downloadPath}", download, torrent);
// Start the download process
var downloadClient = new DownloadClient(download, torrent, downloadPath);
if (ActiveDownloadClients.TryAdd(download.DownloadId, downloadClient))
{
Log($"Starting download", download, torrent);
var remoteId = await downloadClient.Start(Settings.Get);
if (!String.IsNullOrWhiteSpace(remoteId) && download.RemoteId != remoteId)
if (ActiveDownloadClients.Count >= settingDownloadLimit)
{
Log($"Received ID {remoteId}", download, torrent);
Log($"Not starting download because there are already the max number of downloads active", download, torrent);
await _downloads.UpdateRemoteId(download.DownloadId, remoteId);
continue;
}
else
if (ActiveDownloadClients.ContainsKey(download.DownloadId))
{
Log($"No ID received", download, torrent);
Log($"Not starting download because this download is already active", download, torrent);
continue;
}
try
{
Log($"Unrestricting links", download, torrent);
var downloadLink = await _torrents.UnrestrictLink(download.DownloadId);
download.Link = downloadLink;
}
catch (Exception ex)
{
_logger.LogError(ex, $"Cannot unrestrict link: {ex.Message}");
await _downloads.UpdateError(download.DownloadId, ex.Message);
await _downloads.UpdateCompleted(download.DownloadId, DateTimeOffset.UtcNow);
download.Error = ex.Message;
download.Completed = DateTimeOffset.UtcNow;
continue;
}
Log($"Marking download as started", download, torrent);
download.DownloadStarted = DateTime.UtcNow;
await _downloads.UpdateDownloadStarted(download.DownloadId, download.DownloadStarted);
var downloadPath = settingDownloadPath;
if (!String.IsNullOrWhiteSpace(torrent.Category))
{
downloadPath = Path.Combine(downloadPath, torrent.Category);
}
Log($"Setting download path to {downloadPath}", download, torrent);
// Start the download process
var downloadClient = new DownloadClient(download, torrent, downloadPath);
if (ActiveDownloadClients.TryAdd(download.DownloadId, downloadClient))
{
Log($"Starting download", download, torrent);
var remoteId = await downloadClient.Start(Settings.Get);
if (!String.IsNullOrWhiteSpace(remoteId) && download.RemoteId != remoteId)
{
Log($"Received ID {remoteId}", download, torrent);
await _downloads.UpdateRemoteId(download.DownloadId, remoteId);
}
else
{
Log($"No ID received", download, torrent);
}
}
}
}
// Check if there are any unpacks that are queued and can be started.
var queuedUnpacks = torrent.Downloads
.Where(m => m.Completed == null && m.UnpackingQueued != null && m.UnpackingStarted == null && m.Error == null)
.OrderBy(m => m.DownloadQueued)
.ToList();
// Check if there are any unpacks that are queued and can be started.
var queuedUnpacks = torrent.Downloads
.Where(m => m.Completed == null && m.UnpackingQueued != null && m.UnpackingStarted == null && m.Error == null)
.OrderBy(m => m.DownloadQueued)
.ToList();
foreach (var download in queuedUnpacks)
{
Log($"Starting unpack", download, torrent);
if (download.Link == null)
{
Log($"No download link found", download, torrent);
await _downloads.UpdateError(download.DownloadId, "Download Link cannot be null");
await _downloads.UpdateCompleted(download.DownloadId, DateTimeOffset.UtcNow);
continue;
}
// Check if the unpacking process is even needed
var uri = new Uri(download.Link);
var fileName = uri.Segments.Last();
fileName = HttpUtility.UrlDecode(fileName);
Log($"Found file name {fileName}", download, torrent);
var extension = Path.GetExtension(fileName);
if (extension != ".rar")
{
Log($"No need to unpack, setting it as unpacked", download, torrent);
download.UnpackingStarted = DateTimeOffset.UtcNow;
download.UnpackingFinished = DateTimeOffset.UtcNow;
download.Completed = DateTimeOffset.UtcNow;
await _downloads.UpdateUnpackingStarted(download.DownloadId, download.UnpackingStarted);
await _downloads.UpdateUnpackingFinished(download.DownloadId, download.UnpackingFinished);
await _downloads.UpdateCompleted(download.DownloadId, download.Completed);
continue;
}
// Check if we have reached the download limit, if so queue the download, but don't start it.
if (TorrentRunner.ActiveUnpackClients.Count >= settingUnpackLimit)
{
Log($"Not starting unpack because there are already the max number of unpacks active", download, torrent);
continue;
}
if (TorrentRunner.ActiveUnpackClients.ContainsKey(download.DownloadId))
{
Log($"Not starting unpack because this download is already active", download, torrent);
continue;
}
download.UnpackingStarted = DateTimeOffset.UtcNow;
await _downloads.UpdateUnpackingStarted(download.DownloadId, download.UnpackingStarted);
var downloadPath = settingDownloadPath;
if (!String.IsNullOrWhiteSpace(torrent.Category))
{
downloadPath = Path.Combine(downloadPath, torrent.Category);
}
Log($"Setting unpack path to {downloadPath}", download, torrent);
// Start the unpacking process
var unpackClient = new UnpackClient(download, downloadPath);
if (TorrentRunner.ActiveUnpackClients.TryAdd(download.DownloadId, unpackClient))
foreach (var download in queuedUnpacks)
{
Log($"Starting unpack", download, torrent);
unpackClient.Start();
}
}
Log("Processing", torrent);
// If torrent is erroring out on the Real-Debrid side.
if (torrent.RdStatus == RealDebridStatus.Error)
{
Log($"Torrent reported an error: {torrent.RdStatusRaw}", torrent);
Log($"Torrent retry count {torrent.RetryCount}/{TorrentRetryCount}", torrent);
if (torrent.RetryCount < TorrentRetryCount)
{
Log($"Retrying torrent", torrent);
_ = Task.Run(async () =>
if (download.Link == null)
{
using var blockScope = _serviceProvider.CreateScope();
var torrentsService = (Torrents) blockScope.ServiceProvider.GetService(typeof(Torrents));
await torrentsService.RetryTorrent(torrent.TorrentId, torrent.RetryCount + 1);
});
Log($"No download link found", download, torrent);
// Force a delay and return the main loop to prevent concurrency issues.
await Task.Delay(10000);
await _downloads.UpdateError(download.DownloadId, "Download Link cannot be null");
await _downloads.UpdateCompleted(download.DownloadId, DateTimeOffset.UtcNow);
return;
}
continue;
}
Log($"Received RealDebrid error: {torrent.RdStatusRaw}, not processing further", torrent);
await _torrents.UpdateComplete(torrent.TorrentId, DateTimeOffset.Now);
// Check if the unpacking process is even needed
var uri = new Uri(download.Link);
var fileName = uri.Segments.Last();
continue;
}
fileName = HttpUtility.UrlDecode(fileName);
// Real-Debrid is waiting for file selection, select which files to download.
if ((torrent.RdStatus == RealDebridStatus.WaitingForFileSelection || torrent.RdStatus == RealDebridStatus.Finished) &&
torrent.FilesSelected == null &&
torrent.Downloads.Count == 0 &&
torrent.FilesSelected == null)
{
Log($"Selecting files", torrent);
Log($"Found file name {fileName}", download, torrent);
var files = torrent.Files;
var extension = Path.GetExtension(fileName);
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.UpdateFilesSelected(torrent.TorrentId, DateTime.UtcNow);
}
// Real-Debrid finished downloading the torrent, process the file to host.
if (torrent.RdStatus == RealDebridStatus.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);
await _torrents.CheckForLinks(torrent.TorrentId);
}
}
// Check if torrent is complete
if (torrent.Downloads.Count > 0)
{
var allComplete = torrent.Downloads.Count(m => m.Completed != null);
if (allComplete == torrent.Downloads.Count)
{
Log($"All downloads complete, marking torrent as complete", torrent);
await _torrents.UpdateComplete(torrent.TorrentId, DateTimeOffset.UtcNow);
switch (torrent.FinishedAction)
if (extension != ".rar")
{
case TorrentFinishedAction.RemoveAllTorrents:
Log($"Removing torrents from Real-Debrid and Real-Debrid Client, no files", torrent);
await _torrents.Delete(torrent.TorrentId, true, true, false);
break;
case TorrentFinishedAction.RemoveRealDebrid:
Log($"Removing torrents from Real-Debrid, no files", torrent);
await _torrents.Delete(torrent.TorrentId, false, true, false);
break;
case TorrentFinishedAction.None:
Log($"Not removing torrents or files", torrent);
break;
default:
Log($"Invalid torrent FinishedAction {torrent.FinishedAction}", torrent);
break;
Log($"No need to unpack, setting it as unpacked", download, torrent);
download.UnpackingStarted = DateTimeOffset.UtcNow;
download.UnpackingFinished = DateTimeOffset.UtcNow;
download.Completed = DateTimeOffset.UtcNow;
await _downloads.UpdateUnpackingStarted(download.DownloadId, download.UnpackingStarted);
await _downloads.UpdateUnpackingFinished(download.DownloadId, download.UnpackingFinished);
await _downloads.UpdateCompleted(download.DownloadId, download.Completed);
continue;
}
// Check if we have reached the download limit, if so queue the download, but don't start it.
if (TorrentRunner.ActiveUnpackClients.Count >= settingUnpackLimit)
{
Log($"Not starting unpack because there are already the max number of unpacks active", download, torrent);
continue;
}
if (TorrentRunner.ActiveUnpackClients.ContainsKey(download.DownloadId))
{
Log($"Not starting unpack because this download is already active", download, torrent);
continue;
}
download.UnpackingStarted = DateTimeOffset.UtcNow;
await _downloads.UpdateUnpackingStarted(download.DownloadId, download.UnpackingStarted);
var downloadPath = settingDownloadPath;
if (!String.IsNullOrWhiteSpace(torrent.Category))
{
downloadPath = Path.Combine(downloadPath, torrent.Category);
}
Log($"Setting unpack path to {downloadPath}", download, torrent);
// Start the unpacking process
var unpackClient = new UnpackClient(download, downloadPath);
if (TorrentRunner.ActiveUnpackClients.TryAdd(download.DownloadId, unpackClient))
{
Log($"Starting unpack", download, torrent);
unpackClient.Start();
}
}
else
Log("Processing", torrent);
// If torrent is erroring out on the Real-Debrid side.
if (torrent.RdStatus == RealDebridStatus.Error)
{
Log($"Waiting for downloads to complete. {allComplete}/{torrent.Downloads.Count} complete", torrent);
Log($"Torrent reported an error: {torrent.RdStatusRaw}", torrent);
Log($"Torrent retry count {torrent.RetryCount}/{torrent.TorrentRetryAttempts}", torrent);
Log($"Received RealDebrid error: {torrent.RdStatusRaw}, not processing further", torrent);
await _torrents.UpdateComplete(torrent.TorrentId, $"Received RealDebrid error: {torrent.RdStatusRaw}.", DateTimeOffset.UtcNow);
continue;
}
// Real-Debrid is waiting for file selection, select which files to download.
if ((torrent.RdStatus == RealDebridStatus.WaitingForFileSelection || torrent.RdStatus == RealDebridStatus.Finished) &&
torrent.FilesSelected == null &&
torrent.Downloads.Count == 0 &&
torrent.FilesSelected == null)
{
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.UpdateFilesSelected(torrent.TorrentId, DateTime.UtcNow);
}
// Real-Debrid finished downloading the torrent, process the file to host.
if (torrent.RdStatus == RealDebridStatus.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);
await _torrents.CheckForLinks(torrent.TorrentId);
}
}
// Check if torrent is complete
if (torrent.Downloads.Count > 0)
{
var allComplete = torrent.Downloads.Count(m => m.Completed != null);
if (allComplete == torrent.Downloads.Count)
{
Log($"All downloads complete, marking torrent as complete", torrent);
await _torrents.UpdateComplete(torrent.TorrentId, null, DateTimeOffset.UtcNow);
switch (torrent.FinishedAction)
{
case TorrentFinishedAction.RemoveAllTorrents:
Log($"Removing torrents from Real-Debrid and Real-Debrid Client, no files", torrent);
await _torrents.Delete(torrent.TorrentId, true, true, false);
break;
case TorrentFinishedAction.RemoveRealDebrid:
Log($"Removing torrents from Real-Debrid, no files", torrent);
await _torrents.Delete(torrent.TorrentId, false, true, false);
break;
case TorrentFinishedAction.None:
Log($"Not removing torrents or files", torrent);
break;
default:
Log($"Invalid torrent FinishedAction {torrent.FinishedAction}", torrent);
break;
}
}
else
{
Log($"Waiting for downloads to complete. {allComplete}/{torrent.Downloads.Count} complete", torrent);
}
}
}
catch (Exception ex)
{
_logger.LogError(ex.Message, $"Torrent processing result in an unexpected exception: {ex.Message}");
await _torrents.UpdateComplete(torrent.TorrentId, ex.Message, DateTimeOffset.UtcNow);
}
}

View file

@ -9,9 +9,9 @@ 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;
using RdtClient.Service.Models;
using RdtClient.Service.Models.TorrentClient;
using RdtClient.Service.Services.TorrentClients;
using Torrent = RdtClient.Data.Models.Data.Torrent;
@ -93,13 +93,7 @@ namespace RdtClient.Service.Services
await _torrentData.UpdateCategory(torrent.TorrentId, category);
}
public async Task<Torrent> UploadMagnet(String magnetLink,
String category,
TorrentDownloadAction downloadAction,
TorrentFinishedAction finishedAction,
Int32 downloadMinSize,
String downloadManualFiles,
Int32? priority)
public async Task<Torrent> UploadMagnet(String magnetLink, Torrent torrent)
{
MagnetLink magnet;
@ -117,20 +111,14 @@ namespace RdtClient.Service.Services
var hash = magnet.InfoHash.ToHex();
var newTorrent = await Add(id, hash, category, downloadAction, finishedAction, downloadMinSize, downloadManualFiles, magnetLink, false, priority);
var newTorrent = await Add(id, hash, magnetLink, false, torrent);
Log($"Adding {hash} magnet link {magnetLink}", newTorrent);
return newTorrent;
}
public async Task<Torrent> UploadFile(Byte[] bytes,
String category,
TorrentDownloadAction downloadAction,
TorrentFinishedAction finishedAction,
Int32 downloadMinSize,
String downloadManualFiles,
Int32? priority)
public async Task<Torrent> UploadFile(Byte[] bytes, Torrent torrent)
{
MonoTorrent.Torrent monoTorrent;
@ -149,7 +137,7 @@ namespace RdtClient.Service.Services
var hash = monoTorrent.InfoHash.ToHex();
var newTorrent = await Add(id, hash, category, downloadAction, finishedAction, downloadMinSize, downloadManualFiles, fileAsBase64, true, priority);
var newTorrent = await Add(id, hash, fileAsBase64, true, torrent);
Log($"Adding {hash} torrent file {fileAsBase64}", newTorrent);
@ -221,7 +209,7 @@ namespace RdtClient.Service.Services
Log($"Deleting", torrent);
await UpdateComplete(torrentId, DateTimeOffset.UtcNow);
await UpdateComplete(torrentId, "Torrent deleted", DateTimeOffset.UtcNow);
foreach (var download in torrent.Downloads)
{
@ -248,7 +236,6 @@ namespace RdtClient.Service.Services
{
Log($"Deleting RdtClient data", torrent);
await _torrentData.UpdateComplete(torrent.TorrentId, DateTimeOffset.UtcNow);
await _downloads.DeleteForTorrent(torrent.TorrentId);
await _torrentData.Delete(torrentId);
}
@ -380,31 +367,31 @@ namespace RdtClient.Service.Services
{
var torrent = await _torrentData.GetById(torrentId);
if (torrent == null)
if (torrent?.Retry == null)
{
return;
}
Log($"Retrying Torrent", torrent);
await UpdateComplete(torrent.TorrentId, DateTimeOffset.Now);
await UpdateComplete(torrent.TorrentId, "Retrying Torrent", DateTimeOffset.UtcNow);
foreach (var download in torrent.Downloads)
{
await _downloads.UpdateError(download.DownloadId, null);
await _downloads.UpdateCompleted(download.DownloadId, DateTimeOffset.Now);
await _downloads.UpdateCompleted(download.DownloadId, DateTimeOffset.UtcNow);
}
foreach (var download in torrent.Downloads)
{
while (TorrentRunner.ActiveDownloadClients.TryGetValue(download.DownloadId, out var downloadClient))
while (TorrentRunner.ActiveDownloadClients.TryRemove(download.DownloadId, out var downloadClient))
{
await downloadClient.Cancel();
await Task.Delay(100);
}
while (TorrentRunner.ActiveUnpackClients.TryGetValue(download.DownloadId, out var unpackClient))
while (TorrentRunner.ActiveUnpackClients.TryRemove(download.DownloadId, out var unpackClient))
{
unpackClient.Cancel();
@ -425,26 +412,14 @@ namespace RdtClient.Service.Services
{
var bytes = Convert.FromBase64String(torrent.FileOrMagnet);
newTorrent = await UploadFile(bytes,
torrent.Category,
torrent.DownloadAction,
torrent.FinishedAction,
torrent.DownloadMinSize,
torrent.DownloadManualFiles,
torrent.Priority);
newTorrent = await UploadFile(bytes, torrent);
}
else
{
newTorrent = await UploadMagnet(torrent.FileOrMagnet,
torrent.Category,
torrent.DownloadAction,
torrent.FinishedAction,
torrent.DownloadMinSize,
torrent.DownloadManualFiles,
torrent.Priority);
newTorrent = await UploadMagnet(torrent.FileOrMagnet, torrent);
}
await _torrentData.UpdateRetryCount(newTorrent.TorrentId, retryCount);
await _torrentData.UpdateRetry(newTorrent.TorrentId, null, retryCount);
}
finally
{
@ -485,16 +460,16 @@ namespace RdtClient.Service.Services
await FileHelper.Delete(filePath);
await _torrentData.UpdateComplete(download.TorrentId, null);
Log($"Resetting", download, download.Torrent);
await _downloads.Reset(downloadId);
await _torrentData.UpdateComplete(download.TorrentId, null, null);
}
public async Task UpdateComplete(Guid torrentId, DateTimeOffset datetime)
public async Task UpdateComplete(Guid torrentId, String error, DateTimeOffset datetime)
{
await _torrentData.UpdateComplete(torrentId, datetime);
await _torrentData.UpdateComplete(torrentId, error, datetime);
}
public async Task UpdateFilesSelected(Guid torrentId, DateTimeOffset datetime)
@ -514,6 +489,16 @@ namespace RdtClient.Service.Services
await _torrentData.UpdatePriority(torrent.TorrentId, priority);
}
public async Task UpdateRetry(Guid torrentId, DateTimeOffset? datetime, Int32 retry)
{
await _torrentData.UpdateRetry(torrentId, datetime, retry);
}
public async Task UpdateError(Guid torrentId, String error)
{
await _torrentData.UpdateError(torrentId, error);
}
public async Task<Torrent> GetById(Guid torrentId)
{
var torrent = await _torrentData.GetById(torrentId);
@ -558,36 +543,26 @@ namespace RdtClient.Service.Services
private async Task<Torrent> Add(String rdTorrentId,
String infoHash,
String category,
TorrentDownloadAction downloadAction,
TorrentFinishedAction finishedAction,
Int32 downloadMinSize,
String downloadManualFiles,
String fileOrMagnetContents,
Boolean isFile,
Int32? priority)
Torrent torrent)
{
await RealDebridUpdateLock.WaitAsync();
try
{
var torrent = await _torrentData.GetByHash(infoHash);
var existingTorrent = await _torrentData.GetByHash(infoHash);
if (torrent != null)
if (existingTorrent != null)
{
return torrent;
return existingTorrent;
}
var newTorrent = await _torrentData.Add(rdTorrentId,
infoHash,
category,
downloadAction,
finishedAction,
downloadMinSize,
downloadManualFiles,
fileOrMagnetContents,
isFile,
priority);
torrent);
await UpdateRdData(newTorrent);
@ -598,7 +573,7 @@ namespace RdtClient.Service.Services
RealDebridUpdateLock.Release();
}
}
public async Task Update(Torrent torrent)
{
await _torrentData.Update(torrent);
@ -703,7 +678,7 @@ namespace RdtClient.Service.Services
_logger.LogDebug(message);
}
private void Log(String message, Torrent torrent = null)
{
if (torrent != null)

View file

@ -5,8 +5,8 @@ using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using RdtClient.Service.Models.QBittorrent;
using RdtClient.Service.Models.QBittorrent.QuickType;
using RdtClient.Data.Models.QBittorrent;
using RdtClient.Data.Models.QBittorrent.QuickType;
using RdtClient.Service.Services;

View file

@ -5,7 +5,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using RdtClient.Data.Models.Data;
using RdtClient.Service.Models;
using RdtClient.Data.Models.Internal;
using RdtClient.Service.Services;
using Serilog.Events;

View file

@ -7,7 +7,6 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using MonoTorrent;
using RdtClient.Data.Enums;
using RdtClient.Service.Helpers;
using RdtClient.Service.Services;
using Torrent = RdtClient.Data.Models.Data.Torrent;
@ -91,7 +90,7 @@ namespace RdtClient.Web.Controllers
var bytes = memoryStream.ToArray();
await _torrents.UploadFile(bytes, formData.Category, formData.DownloadAction, formData.FinishedAction, formData.DownloadMinSize, formData.DownloadManualFiles, formData.Priority);
await _torrents.UploadFile(bytes, formData.Torrent);
return Ok();
}
@ -100,13 +99,7 @@ namespace RdtClient.Web.Controllers
[Route("UploadMagnet")]
public async Task<ActionResult> UploadMagnet([FromBody] TorrentControllerUploadMagnetRequest request)
{
await _torrents.UploadMagnet(request.MagnetLink,
request.Category,
request.DownloadAction,
request.FinishedAction,
request.DownloadMinSize,
request.DownloadManualFiles,
request.Priority);
await _torrents.UploadMagnet(request.MagnetLink, request.Torrent);
return Ok();
}
@ -185,23 +178,13 @@ namespace RdtClient.Web.Controllers
public class TorrentControllerUploadFileRequest
{
public String Category { get; set; }
public TorrentDownloadAction DownloadAction { get; set; }
public TorrentFinishedAction FinishedAction { get; set; }
public Int32 DownloadMinSize { get; set; }
public String DownloadManualFiles { get; set; }
public Int32? Priority { get; set; }
public Torrent Torrent { get; set; }
}
public class TorrentControllerUploadMagnetRequest
{
public String MagnetLink { get; set; }
public String Category { get; set; }
public TorrentDownloadAction DownloadAction { get; set; }
public TorrentFinishedAction FinishedAction { get; set; }
public Int32 DownloadMinSize { get; set; }
public String DownloadManualFiles { get; set; }
public Int32? Priority { get; set; }
public Torrent Torrent { get; set; }
}
public class TorrentControllerDeleteRequest