Add priority mechanishm and support for sonarr priority.

Changed to have /torrents/files return only files that are selected.
Add pause/unpause from Sonarr/Radar. Works only for Aria2.
This commit is contained in:
Roger Far 2021-10-24 10:40:59 -06:00
parent e9e16bedf0
commit 6ac0265d2d
24 changed files with 898 additions and 51 deletions

View file

@ -29,6 +29,7 @@
[(ngModel)]="magnetLink" [(ngModel)]="magnetLink"
[disabled]="saving" [disabled]="saving"
(blur)="checkFiles()" (blur)="checkFiles()"
(paste)="onPaste()"
></textarea> ></textarea>
</div> </div>
</div> </div>
@ -79,6 +80,16 @@
</div> </div>
<p class="help">The category becomes a sub-folder in your main download path.</p> <p class="help">The category becomes a sub-folder in your main download path.</p>
</div> </div>
<div class="field">
<label class="label">Priority</label>
<div class="control">
<input class="input" type="number" [(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> </div>
<div fxFlex> <div fxFlex>
<div class="field"> <div class="field">

View file

@ -14,6 +14,7 @@ export class AddNewTorrentComponent implements OnInit {
private currentTorrentFile: string; private currentTorrentFile: string;
public category: string; public category: string;
public priority: number;
public downloadAction: number = 0; public downloadAction: number = 0;
public finishedAction: number = 0; public finishedAction: number = 0;
@ -98,7 +99,8 @@ export class AddNewTorrentComponent implements OnInit {
this.downloadAction, this.downloadAction,
this.finishedAction, this.finishedAction,
this.downloadMinSize, this.downloadMinSize,
downloadManualFiles downloadManualFiles,
this.priority
) )
.subscribe( .subscribe(
() => { () => {
@ -117,7 +119,8 @@ export class AddNewTorrentComponent implements OnInit {
this.downloadAction, this.downloadAction,
this.finishedAction, this.finishedAction,
this.downloadMinSize, this.downloadMinSize,
downloadManualFiles downloadManualFiles,
this.priority
) )
.subscribe( .subscribe(
() => { () => {
@ -134,6 +137,12 @@ export class AddNewTorrentComponent implements OnInit {
} }
} }
public onPaste(): void {
setTimeout(() => {
this.checkFiles();
}, 100);
}
public checkFiles(): void { public checkFiles(): void {
if (this.magnetLink && this.magnetLink === this.currentTorrentFile) { if (this.magnetLink && this.magnetLink === this.currentTorrentFile) {
return; return;

View file

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

View file

@ -8,6 +8,7 @@
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Category</th> <th>Category</th>
<th>Priority</th>
<th>Files</th> <th>Files</th>
<th>Downloads</th> <th>Downloads</th>
<th>Size</th> <th>Size</th>
@ -22,6 +23,9 @@
<td> <td>
{{ torrent.category }} {{ torrent.category }}
</td> </td>
<td>
{{ torrent.priority }}
</td>
<td> <td>
{{ torrent.files.length | number }} {{ torrent.files.length | number }}
</td> </td>

View file

@ -43,7 +43,8 @@ export class TorrentService {
downloadAction: number, downloadAction: number,
finishedAction: number, finishedAction: number,
downloadMinSize: number, downloadMinSize: number,
downloadManualFiles: string downloadManualFiles: string,
priority: number
): Observable<void> { ): Observable<void> {
return this.http.post<void>(`/Api/Torrents/UploadMagnet`, { return this.http.post<void>(`/Api/Torrents/UploadMagnet`, {
magnetLink, magnetLink,
@ -52,6 +53,7 @@ export class TorrentService {
finishedAction, finishedAction,
downloadMinSize, downloadMinSize,
downloadManualFiles, downloadManualFiles,
priority,
}); });
} }
@ -61,13 +63,14 @@ export class TorrentService {
downloadAction: number, downloadAction: number,
finishedAction: number, finishedAction: number,
downloadMinSize: number, downloadMinSize: number,
downloadManualFiles: string downloadManualFiles: string,
priority: number
): Observable<void> { ): Observable<void> {
const formData: FormData = new FormData(); const formData: FormData = new FormData();
formData.append('file', file); formData.append('file', file);
formData.append( formData.append(
'formData', 'formData',
JSON.stringify({ category, downloadAction, finishedAction, downloadMinSize, downloadManualFiles }) JSON.stringify({ category, downloadAction, finishedAction, downloadMinSize, downloadManualFiles, priority })
); );
return this.http.post<void>(`/Api/Torrents/UploadFile`, formData); return this.http.post<void>(`/Api/Torrents/UploadFile`, formData);
} }
@ -104,4 +107,8 @@ export class TorrentService {
public retryDownload(downloadId: string): Observable<void> { public retryDownload(downloadId: string): Observable<void> {
return this.http.post<void>(`/Api/Torrents/RetryDownload/${downloadId}`, {}); return this.http.post<void>(`/Api/Torrents/RetryDownload/${downloadId}`, {});
} }
public update(torrent: Torrent): Observable<void> {
return this.http.put<void>(`/Api/Torrents/Update`, torrent);
}
} }

View file

@ -27,6 +27,9 @@
<div class="control"> <div class="control">
<button class="button is-primary" (click)="showRetryModal()">Retry Torrent</button> <button class="button is-primary" (click)="showRetryModal()">Retry Torrent</button>
</div> </div>
<div class="control">
<button class="button is-light" (click)="showUpdateSettingsModal()">Change priority</button>
</div>
</div> </div>
<div class="field"> <div class="field">
<label class="label">Status</label> <label class="label">Status</label>
@ -40,6 +43,10 @@
<label class="label">Hash</label> <label class="label">Hash</label>
{{ torrent.hash }} {{ torrent.hash }}
</div> </div>
<div class="field">
<label class="label">Priority</label>
{{ torrent.priority || "" }}
</div>
<div class="field"> <div class="field">
<label class="label">Category</label> <label class="label">Category</label>
{{ torrent.category || "(no category set)" }} {{ torrent.category || "(no category set)" }}
@ -445,3 +452,42 @@
</footer> </footer>
</div> </div>
</div> </div>
<div class="modal" [class.is-active]="isUpdateSettingsModalActive">
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Update torrent settings</p>
<button class="delete" aria-label="close" (click)="updateSettingsCancel()"></button>
</header>
<section class="modal-card-body">
<div class="field">
<label class="label">Priority</label>
<div class="control">
<input class="input" type="number" [(ngModel)]="updateSettingsPriority" />
</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>
</section>
<footer class="modal-card-foot">
<button
class="button is-success"
(click)="updateSettingsOk()"
[disabled]="updating"
[ngClass]="{ 'is-loading': updating }"
>
Retry
</button>
<button
class="button"
(click)="updateSettingsCancel()"
[disabled]="updating"
[ngClass]="{ 'is-loading': updating }"
>
Cancel
</button>
</footer>
</div>
</div>

View file

@ -34,6 +34,10 @@ export class TorrentComponent implements OnInit {
public downloadRetrying: boolean; public downloadRetrying: boolean;
public downloadRetryId: string; public downloadRetryId: string;
public isUpdateSettingsModalActive: boolean;
public updateSettingsPriority: number;
public updating: boolean;
constructor(private activatedRoute: ActivatedRoute, private router: Router, private torrentService: TorrentService) {} constructor(private activatedRoute: ActivatedRoute, private router: Router, private torrentService: TorrentService) {}
ngOnInit(): void { ngOnInit(): void {
@ -163,4 +167,31 @@ export class TorrentComponent implements OnInit {
} }
); );
} }
public showUpdateSettingsModal(): void {
this.updateSettingsPriority = this.torrent.priority;
this.isUpdateSettingsModalActive = true;
}
public updateSettingsCancel(): void {
this.isUpdateSettingsModalActive = false;
}
public updateSettingsOk(): void {
this.updating = true;
this.torrent.priority = this.updateSettingsPriority;
this.torrentService.update(this.torrent).subscribe(
() => {
this.isUpdateSettingsModalActive = false;
this.updating = false;
},
() => {
this.isUpdateSettingsModalActive = false;
this.updating = false;
}
);
}
} }

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -15,6 +16,14 @@ namespace RdtClient.Data.Data
_dataContext = dataContext; _dataContext = dataContext;
} }
public async Task<List<Download>> GetForTorrent(Guid torrentId)
{
return await _dataContext.Downloads
.AsNoTracking()
.Where(m => m.TorrentId == torrentId)
.ToListAsync();
}
public async Task<Download> GetById(Guid downloadId) public async Task<Download> GetById(Guid downloadId)
{ {
return await _dataContext.Downloads return await _dataContext.Downloads

View file

@ -27,15 +27,12 @@ namespace RdtClient.Data.Data
try try
{ {
if (_torrentCache == null) _torrentCache ??= await _dataContext.Torrents
{ .AsNoTracking()
_torrentCache = await _dataContext.Torrents .Include(m => m.Downloads)
.AsNoTracking() .ToListAsync();
.Include(m => m.Downloads)
.ToListAsync();
}
return _torrentCache.OrderByDescending(m => m.Added).ToList(); return _torrentCache.OrderBy(m => m.Priority ?? 9999).ThenBy(m => m.Added).ToList();
} }
finally finally
{ {
@ -91,7 +88,8 @@ namespace RdtClient.Data.Data
Int32 downloadMinSize, Int32 downloadMinSize,
String downloadManualFiles, String downloadManualFiles,
String fileOrMagnetContents, String fileOrMagnetContents,
Boolean isFile) Boolean isFile,
Int32? priority)
{ {
var torrent = new Torrent var torrent = new Torrent
{ {
@ -105,7 +103,8 @@ namespace RdtClient.Data.Data
DownloadMinSize = downloadMinSize, DownloadMinSize = downloadMinSize,
DownloadManualFiles = downloadManualFiles, DownloadManualFiles = downloadManualFiles,
FileOrMagnet = fileOrMagnetContents, FileOrMagnet = fileOrMagnetContents,
IsFile = isFile IsFile = isFile,
Priority = priority
}; };
await _dataContext.Torrents.AddAsync(torrent); await _dataContext.Torrents.AddAsync(torrent);
@ -148,6 +147,22 @@ namespace RdtClient.Data.Data
await VoidCache(); await VoidCache();
} }
public async Task Update(Torrent torrent)
{
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrent.TorrentId);
if (dbTorrent == null)
{
return;
}
dbTorrent.Priority = torrent.Priority;
await _dataContext.SaveChangesAsync();
await VoidCache();
}
public async Task UpdateCategory(Guid torrentId, String category) public async Task UpdateCategory(Guid torrentId, String category)
{ {
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId); var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
@ -212,6 +227,22 @@ namespace RdtClient.Data.Data
await VoidCache(); await VoidCache();
} }
public async Task UpdatePriority(Guid torrentId, Int32? priority)
{
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
if (dbTorrent == null)
{
return;
}
dbTorrent.Priority = priority;
await _dataContext.SaveChangesAsync();
await VoidCache();
}
public async Task Delete(Guid torrentId) public async Task Delete(Guid torrentId)
{ {
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId); var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);

View file

@ -0,0 +1,442 @@
// <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("20211024161405_Torrents_Add_Priority")]
partial class Torrents_Add_Priority
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "5.0.10");
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<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_Add_Priority : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "Priority",
table: "Torrents",
type: "INTEGER",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Priority",
table: "Torrents");
}
}
}

View file

@ -318,6 +318,9 @@ namespace RdtClient.Data.Migrations
b.Property<bool>("IsFile") b.Property<bool>("IsFile")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<int?>("Priority")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset>("RdAdded") b.Property<DateTimeOffset>("RdAdded")
.HasColumnType("TEXT"); .HasColumnType("TEXT");

View file

@ -28,7 +28,8 @@ namespace RdtClient.Data.Models.Data
public String FileOrMagnet { get; set; } public String FileOrMagnet { get; set; }
public Boolean IsFile { get; set; } public Boolean IsFile { get; set; }
public Int32? Priority { get; set; }
public Int32 RetryCount { get; set; } public Int32 RetryCount { get; set; }
[InverseProperty("Torrent")] [InverseProperty("Torrent")]

View file

@ -84,9 +84,31 @@ namespace RdtClient.Service.Services
} }
} }
public void Cancel() public async Task Cancel()
{ {
_downloader?.Cancel(); if (_downloader == null)
{
return;
}
await _downloader.Cancel();
}
public async Task Pause()
{
if (_downloader == null)
{
return;
}
await _downloader.Pause();
}
public async Task Resume()
{
if (_downloader == null)
{
return;
}
await _downloader.Resume();
} }
} }
} }

View file

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Timers; using System.Timers;
using Aria2NET; using Aria2NET;
@ -29,7 +30,12 @@ namespace RdtClient.Service.Services.Downloaders
_uri = uri; _uri = uri;
_filePath = filePath; _filePath = filePath;
_aria2NetClient = new Aria2NetClient(settings.Aria2cUrl, settings.Aria2cSecret); var httpClient = new HttpClient
{
Timeout = TimeSpan.FromSeconds(1)
};
_aria2NetClient = new Aria2NetClient(settings.Aria2cUrl, settings.Aria2cSecret, httpClient);
_timer = new Timer(); _timer = new Timer();
@ -126,6 +132,40 @@ namespace RdtClient.Service.Services.Downloaders
} }
} }
public async Task Pause()
{
if (String.IsNullOrWhiteSpace(_gid))
{
return;
}
try
{
await _aria2NetClient.Pause(_gid);
}
catch
{
// ignored
}
}
public async Task Resume()
{
if (String.IsNullOrWhiteSpace(_gid))
{
return;
}
try
{
await _aria2NetClient.Unpause(_gid);
}
catch
{
// ignored
}
}
private async void OnTimedEvent(Object source, ElapsedEventArgs e) private async void OnTimedEvent(Object source, ElapsedEventArgs e)
{ {
if (_gid == null) if (_gid == null)

View file

@ -21,5 +21,7 @@ namespace RdtClient.Service.Services.Downloaders
event EventHandler<DownloadProgressEventArgs> DownloadProgress; event EventHandler<DownloadProgressEventArgs> DownloadProgress;
Task<String> Download(); Task<String> Download();
Task Cancel(); Task Cancel();
Task Pause();
Task Resume();
} }
} }

View file

@ -124,5 +124,15 @@ namespace RdtClient.Service.Services.Downloaders
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task Pause()
{
return Task.CompletedTask;
}
public Task Resume()
{
return Task.CompletedTask;
}
} }
} }

View file

@ -143,5 +143,15 @@ namespace RdtClient.Service.Services.Downloaders
}); });
} }
} }
public Task Pause()
{
return Task.CompletedTask;
}
public Task Resume()
{
return Task.CompletedTask;
}
} }
} }

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using RdtClient.Data.Data; using RdtClient.Data.Data;
using Download = RdtClient.Data.Models.Data.Download; using Download = RdtClient.Data.Models.Data.Download;
@ -13,7 +14,12 @@ namespace RdtClient.Service.Services
{ {
_downloadData = downloadData; _downloadData = downloadData;
} }
public async Task<List<Download>> GetForTorrent(Guid torrentId)
{
return await _downloadData.GetForTorrent(torrentId);
}
public async Task<Download> GetById(Guid downloadId) public async Task<Download> GetById(Guid downloadId)
{ {
return await _downloadData.GetById(downloadId); return await _downloadData.GetById(downloadId);

View file

@ -14,12 +14,14 @@ namespace RdtClient.Service.Services
private readonly Authentication _authentication; private readonly Authentication _authentication;
private readonly Settings _settings; private readonly Settings _settings;
private readonly Torrents _torrents; private readonly Torrents _torrents;
private readonly Downloads _downloads;
public QBittorrent(Settings settings, Authentication authentication, Torrents torrents) public QBittorrent(Settings settings, Authentication authentication, Torrents torrents, Downloads downloads)
{ {
_settings = settings; _settings = settings;
_authentication = authentication; _authentication = authentication;
_torrents = torrents; _torrents = torrents;
_downloads = downloads;
} }
public async Task<Boolean> AuthLogin(String userName, String password) public async Task<Boolean> AuthLogin(String userName, String password)
@ -291,7 +293,7 @@ namespace RdtClient.Service.Services
Upspeed = speed Upspeed = speed
}; };
if (torrent.Completed.HasValue) if (torrent.Completed.HasValue && torrent.Downloads.All(m => m.Completed.HasValue))
{ {
// Indicates completed torrent and not seeding anymore. // Indicates completed torrent and not seeding anymore.
result.State = "pausedUP"; result.State = "pausedUP";
@ -329,7 +331,7 @@ namespace RdtClient.Service.Services
return null; return null;
} }
foreach (var file in torrent.Files) foreach (var file in torrent.Files.Where(m => m.Selected))
{ {
var result = new TorrentFileItem var result = new TorrentFileItem
{ {
@ -421,18 +423,18 @@ namespace RdtClient.Service.Services
await _torrents.Delete(torrent.TorrentId, true, true, deleteFiles); await _torrents.Delete(torrent.TorrentId, true, true, deleteFiles);
} }
public async Task TorrentsAddMagnet(String magnetLink, String category) public async Task TorrentsAddMagnet(String magnetLink, String category, Int32? priority)
{ {
var downloadAction = Settings.Get.OnlyDownloadAvailableFiles == 1 ? TorrentDownloadAction.DownloadAvailableFiles : TorrentDownloadAction.DownloadAll; var downloadAction = Settings.Get.OnlyDownloadAvailableFiles == 1 ? TorrentDownloadAction.DownloadAvailableFiles : TorrentDownloadAction.DownloadAll;
await _torrents.UploadMagnet(magnetLink, category, downloadAction, TorrentFinishedAction.None, Settings.Get.MinFileSize, null); await _torrents.UploadMagnet(magnetLink, category, downloadAction, TorrentFinishedAction.None, Settings.Get.MinFileSize, null, priority);
} }
public async Task TorrentsAddFile(Byte[] fileBytes, String category) public async Task TorrentsAddFile(Byte[] fileBytes, String category, Int32? priority)
{ {
var downloadAction = Settings.Get.OnlyDownloadAvailableFiles == 1 ? TorrentDownloadAction.DownloadAvailableFiles : TorrentDownloadAction.DownloadAll; var downloadAction = Settings.Get.OnlyDownloadAvailableFiles == 1 ? TorrentDownloadAction.DownloadAvailableFiles : TorrentDownloadAction.DownloadAll;
await _torrents.UploadFile(fileBytes, category, downloadAction, TorrentFinishedAction.None, Settings.Get.MinFileSize, null); await _torrents.UploadFile(fileBytes, category, downloadAction, TorrentFinishedAction.None, Settings.Get.MinFileSize, null, priority);
} }
public async Task TorrentsSetCategory(String hash, String category) public async Task TorrentsSetCategory(String hash, String category)
@ -515,5 +517,50 @@ namespace RdtClient.Service.Services
await _settings.UpdateString("Categories", categoriesSetting); await _settings.UpdateString("Categories", categoriesSetting);
} }
public async Task TorrentsTopPrio(String hash)
{
await _torrents.UpdatePriority(hash, 1);
}
public async Task TorrentPause(String hash)
{
var torrent = await _torrents.GetByHash(hash);
if (torrent == null)
{
return;
}
var downloads = await _downloads.GetForTorrent(torrent.TorrentId);
foreach (var download in downloads)
{
if (TorrentRunner.ActiveDownloadClients.TryGetValue(download.DownloadId, out var downloadClient))
{
downloadClient.Pause();
}
}
}
public async Task TorrentResume(String hash)
{
var torrent = await _torrents.GetByHash(hash);
if (torrent == null)
{
return;
}
var downloads = await _downloads.GetForTorrent(torrent.TorrentId);
foreach (var download in downloads)
{
if (TorrentRunner.ActiveDownloadClients.TryGetValue(download.DownloadId, out var downloadClient))
{
downloadClient.Resume();
}
}
}
} }
} }

View file

@ -194,7 +194,7 @@ namespace RdtClient.Service.Services
_nextUpdate = DateTime.UtcNow.AddSeconds(updateTime); _nextUpdate = DateTime.UtcNow.AddSeconds(updateTime);
await _torrents.Update(); await _torrents.UpdateRdData();
// Re-get torrents to account for updated info // Re-get torrents to account for updated info
torrents = await _torrents.Get(); torrents = await _torrents.Get();
@ -297,6 +297,7 @@ namespace RdtClient.Service.Services
if (download.Link == null) if (download.Link == null)
{ {
await _downloads.UpdateError(download.DownloadId, "Download Link cannot be null"); await _downloads.UpdateError(download.DownloadId, "Download Link cannot be null");
await _downloads.UpdateCompleted(download.DownloadId, DateTimeOffset.UtcNow);
continue; continue;
} }

View file

@ -83,7 +83,7 @@ namespace RdtClient.Service.Services
if (torrent != null) if (torrent != null)
{ {
await Update(torrent); await UpdateRdData(torrent);
} }
return torrent; return torrent;
@ -106,7 +106,8 @@ namespace RdtClient.Service.Services
TorrentDownloadAction downloadAction, TorrentDownloadAction downloadAction,
TorrentFinishedAction finishedAction, TorrentFinishedAction finishedAction,
Int32 downloadMinSize, Int32 downloadMinSize,
String downloadManualFiles) String downloadManualFiles,
Int32? priority)
{ {
MagnetLink magnet; MagnetLink magnet;
@ -121,7 +122,7 @@ namespace RdtClient.Service.Services
var rdTorrent = await GetRdNetClient().Torrents.AddMagnetAsync(magnetLink); var rdTorrent = await GetRdNetClient().Torrents.AddMagnetAsync(magnetLink);
return await Add(rdTorrent.Id, magnet.InfoHash.ToHex(), category, downloadAction, finishedAction, downloadMinSize, downloadManualFiles, magnetLink, false); return await Add(rdTorrent.Id, magnet.InfoHash.ToHex(), category, downloadAction, finishedAction, downloadMinSize, downloadManualFiles, magnetLink, false, priority);
} }
public async Task<Torrent> UploadFile(Byte[] bytes, public async Task<Torrent> UploadFile(Byte[] bytes,
@ -129,7 +130,8 @@ namespace RdtClient.Service.Services
TorrentDownloadAction downloadAction, TorrentDownloadAction downloadAction,
TorrentFinishedAction finishedAction, TorrentFinishedAction finishedAction,
Int32 downloadMinSize, Int32 downloadMinSize,
String downloadManualFiles) String downloadManualFiles,
Int32? priority)
{ {
MonoTorrent.Torrent torrent; MonoTorrent.Torrent torrent;
@ -146,7 +148,7 @@ namespace RdtClient.Service.Services
var rdTorrent = await GetRdNetClient().Torrents.AddFileAsync(bytes); var rdTorrent = await GetRdNetClient().Torrents.AddFileAsync(bytes);
return await Add(rdTorrent.Id, torrent.InfoHash.ToHex(), category, downloadAction, finishedAction, downloadMinSize, downloadManualFiles, fileAsBase64, true); return await Add(rdTorrent.Id, torrent.InfoHash.ToHex(), category, downloadAction, finishedAction, downloadMinSize, downloadManualFiles, fileAsBase64, true, priority);
} }
public async Task<List<TorrentInstantAvailabilityFile>> GetAvailableFiles(String hash) public async Task<List<TorrentInstantAvailabilityFile>> GetAvailableFiles(String hash)
@ -373,7 +375,7 @@ namespace RdtClient.Service.Services
return profile; return profile;
} }
public async Task Update() public async Task UpdateRdData()
{ {
await RealDebridUpdateLock.WaitAsync(); await RealDebridUpdateLock.WaitAsync();
@ -392,7 +394,7 @@ namespace RdtClient.Service.Services
continue; continue;
} }
await Update(torrent); await UpdateRdData(torrent);
} }
foreach (var torrent in torrents) foreach (var torrent in torrents)
@ -457,7 +459,7 @@ namespace RdtClient.Service.Services
{ {
var bytes = Convert.FromBase64String(torrent.FileOrMagnet); var bytes = Convert.FromBase64String(torrent.FileOrMagnet);
newTorrent = await UploadFile(bytes, torrent.Category, torrent.DownloadAction, torrent.FinishedAction, torrent.DownloadMinSize, torrent.DownloadManualFiles); newTorrent = await UploadFile(bytes, torrent.Category, torrent.DownloadAction, torrent.FinishedAction, torrent.DownloadMinSize, torrent.DownloadManualFiles, torrent.Priority);
} }
else else
{ {
@ -466,7 +468,8 @@ namespace RdtClient.Service.Services
torrent.DownloadAction, torrent.DownloadAction,
torrent.FinishedAction, torrent.FinishedAction,
torrent.DownloadMinSize, torrent.DownloadMinSize,
torrent.DownloadManualFiles); torrent.DownloadManualFiles,
torrent.Priority);
} }
await _torrentData.UpdateRetryCount(newTorrent.TorrentId, newRetryCount); await _torrentData.UpdateRetryCount(newTorrent.TorrentId, newRetryCount);
@ -527,6 +530,18 @@ namespace RdtClient.Service.Services
await _torrentData.UpdateFilesSelected(torrentId, datetime); await _torrentData.UpdateFilesSelected(torrentId, datetime);
} }
public async Task UpdatePriority(String hash, Int32 priority)
{
var torrent = await _torrentData.GetByHash(hash);
if (torrent == null)
{
return;
}
await _torrentData.UpdatePriority(torrent.TorrentId, priority);
}
public async Task<Torrent> GetById(Guid torrentId) public async Task<Torrent> GetById(Guid torrentId)
{ {
var torrent = await _torrentData.GetById(torrentId); var torrent = await _torrentData.GetById(torrentId);
@ -536,7 +551,7 @@ namespace RdtClient.Service.Services
return null; return null;
} }
await Update(torrent); await UpdateRdData(torrent);
foreach (var download in torrent.Downloads) foreach (var download in torrent.Downloads)
{ {
@ -577,7 +592,8 @@ namespace RdtClient.Service.Services
Int32 downloadMinSize, Int32 downloadMinSize,
String downloadManualFiles, String downloadManualFiles,
String fileOrMagnetContents, String fileOrMagnetContents,
Boolean isFile) Boolean isFile,
Int32? priority)
{ {
await RealDebridUpdateLock.WaitAsync(); await RealDebridUpdateLock.WaitAsync();
@ -598,9 +614,10 @@ namespace RdtClient.Service.Services
downloadMinSize, downloadMinSize,
downloadManualFiles, downloadManualFiles,
fileOrMagnetContents, fileOrMagnetContents,
isFile); isFile,
priority);
await Update(newTorrent); await UpdateRdData(newTorrent);
return newTorrent; return newTorrent;
} }
@ -609,8 +626,13 @@ namespace RdtClient.Service.Services
RealDebridUpdateLock.Release(); RealDebridUpdateLock.Release();
} }
} }
public async Task Update(Torrent torrent)
{
await _torrentData.Update(torrent);
}
private async Task Update(Torrent torrent) private async Task UpdateRdData(Torrent torrent)
{ {
var originalTorrent = JsonConvert.SerializeObject(torrent, var originalTorrent = JsonConvert.SerializeObject(torrent,
new JsonSerializerSettings new JsonSerializerSettings

View file

@ -190,21 +190,49 @@ namespace RdtClient.Web.Controllers
[Authorize] [Authorize]
[Route("torrents/pause")] [Route("torrents/pause")]
[HttpGet] [HttpGet]
[HttpPost] public async Task<ActionResult> TorrentsPause([FromQuery] QBTorrentsHashesRequest request)
public ActionResult TorrentsPause()
{ {
var hashes = request.Hashes.Split("|");
foreach (var hash in hashes)
{
await _qBittorrent.TorrentPause(hash);
}
return Ok(); return Ok();
} }
[Authorize]
[Route("torrents/topPrio")]
[HttpPost]
public async Task<ActionResult> TorrentsPausePost([FromForm] QBTorrentsHashesRequest request)
{
return await TorrentsPause(request);
}
[Authorize] [Authorize]
[Route("torrents/resume")] [Route("torrents/resume")]
[HttpGet] [HttpGet]
[HttpPost] public async Task<ActionResult> TorrentsResume([FromQuery] QBTorrentsHashesRequest request)
public ActionResult TorrentsResume()
{ {
var hashes = request.Hashes.Split("|");
foreach (var hash in hashes)
{
await _qBittorrent.TorrentResume(hash);
}
return Ok(); return Ok();
} }
[Authorize]
[Route("torrents/topPrio")]
[HttpPost]
public async Task<ActionResult> TorrentsResumePost([FromForm] QBTorrentsHashesRequest request)
{
return await TorrentsResume(request);
}
[Authorize] [Authorize]
[Route("torrents/setShareLimits")] [Route("torrents/setShareLimits")]
[HttpGet] [HttpGet]
@ -248,13 +276,13 @@ namespace RdtClient.Web.Controllers
{ {
if (url.StartsWith("magnet")) if (url.StartsWith("magnet"))
{ {
await _qBittorrent.TorrentsAddMagnet(url.Trim(), request.Category); await _qBittorrent.TorrentsAddMagnet(url.Trim(), request.Category, null);
} }
else if (url.StartsWith("http")) else if (url.StartsWith("http"))
{ {
var httpClient = new HttpClient(); var httpClient = new HttpClient();
var result = await httpClient.GetByteArrayAsync(url); var result = await httpClient.GetByteArrayAsync(url);
await _qBittorrent.TorrentsAddFile(result, request.Category); await _qBittorrent.TorrentsAddFile(result, request.Category, null);
} }
else else
{ {
@ -279,7 +307,7 @@ namespace RdtClient.Web.Controllers
await file.CopyToAsync(target); await file.CopyToAsync(target);
var fileBytes = target.ToArray(); var fileBytes = target.ToArray();
await _qBittorrent.TorrentsAddFile(fileBytes, request.Category); await _qBittorrent.TorrentsAddFile(fileBytes, request.Category, request.Priority);
} }
} }
@ -370,6 +398,29 @@ namespace RdtClient.Web.Controllers
{ {
return Ok(); return Ok();
} }
[Authorize]
[Route("torrents/topPrio")]
[HttpGet]
public async Task<ActionResult> TorrentsTopPrio([FromQuery] QBTorrentsHashesRequest request)
{
var hashes = request.Hashes.Split("|");
foreach (var hash in hashes)
{
await _qBittorrent.TorrentsTopPrio(hash);
}
return Ok();
}
[Authorize]
[Route("torrents/topPrio")]
[HttpPost]
public async Task<ActionResult> TorrentsTopPrioPost([FromForm] QBTorrentsHashesRequest request)
{
return await TorrentsTopPrio(request);
}
} }
public class QBAuthLoginRequest public class QBAuthLoginRequest
@ -393,6 +444,7 @@ namespace RdtClient.Web.Controllers
{ {
public String Urls { get; set; } public String Urls { get; set; }
public String Category { get; set; } public String Category { get; set; }
public Int32? Priority { get; set; }
} }
public class QBTorrentsSetCategoryRequest public class QBTorrentsSetCategoryRequest
@ -410,4 +462,9 @@ namespace RdtClient.Web.Controllers
{ {
public String Categories { get; set; } public String Categories { get; set; }
} }
public class QBTorrentsHashesRequest
{
public String Hashes { get; set; }
}
} }

View file

@ -91,7 +91,7 @@ namespace RdtClient.Web.Controllers
var bytes = memoryStream.ToArray(); var bytes = memoryStream.ToArray();
await _torrents.UploadFile(bytes, formData.Category, formData.DownloadAction, formData.FinishedAction, formData.DownloadMinSize, formData.DownloadManualFiles); await _torrents.UploadFile(bytes, formData.Category, formData.DownloadAction, formData.FinishedAction, formData.DownloadMinSize, formData.DownloadManualFiles, formData.Priority);
return Ok(); return Ok();
} }
@ -105,7 +105,8 @@ namespace RdtClient.Web.Controllers
request.DownloadAction, request.DownloadAction,
request.FinishedAction, request.FinishedAction,
request.DownloadMinSize, request.DownloadMinSize,
request.DownloadManualFiles); request.DownloadManualFiles,
request.Priority);
return Ok(); return Ok();
} }
@ -171,6 +172,15 @@ namespace RdtClient.Web.Controllers
return Ok(); return Ok();
} }
[HttpPut]
[Route("Update")]
public async Task<ActionResult> Update([FromBody] Torrent torrent)
{
await _torrents.Update(torrent);
return Ok();
}
} }
public class TorrentControllerUploadFileRequest public class TorrentControllerUploadFileRequest
@ -180,6 +190,7 @@ namespace RdtClient.Web.Controllers
public TorrentFinishedAction FinishedAction { get; set; } public TorrentFinishedAction FinishedAction { get; set; }
public Int32 DownloadMinSize { get; set; } public Int32 DownloadMinSize { get; set; }
public String DownloadManualFiles { get; set; } public String DownloadManualFiles { get; set; }
public Int32? Priority { get; set; }
} }
public class TorrentControllerUploadMagnetRequest public class TorrentControllerUploadMagnetRequest
@ -190,6 +201,7 @@ namespace RdtClient.Web.Controllers
public TorrentFinishedAction FinishedAction { get; set; } public TorrentFinishedAction FinishedAction { get; set; }
public Int32 DownloadMinSize { get; set; } public Int32 DownloadMinSize { get; set; }
public String DownloadManualFiles { get; set; } public String DownloadManualFiles { get; set; }
public Int32? Priority { get; set; }
} }
public class TorrentControllerDeleteRequest public class TorrentControllerDeleteRequest