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

View file

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

View file

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

View file

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

View file

@ -43,7 +43,8 @@ export class TorrentService {
downloadAction: number,
finishedAction: number,
downloadMinSize: number,
downloadManualFiles: string
downloadManualFiles: string,
priority: number
): Observable<void> {
return this.http.post<void>(`/Api/Torrents/UploadMagnet`, {
magnetLink,
@ -52,6 +53,7 @@ export class TorrentService {
finishedAction,
downloadMinSize,
downloadManualFiles,
priority,
});
}
@ -61,13 +63,14 @@ export class TorrentService {
downloadAction: number,
finishedAction: number,
downloadMinSize: number,
downloadManualFiles: string
downloadManualFiles: string,
priority: number
): Observable<void> {
const formData: FormData = new FormData();
formData.append('file', file);
formData.append(
'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);
}
@ -104,4 +107,8 @@ export class TorrentService {
public retryDownload(downloadId: string): Observable<void> {
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">
<button class="button is-primary" (click)="showRetryModal()">Retry Torrent</button>
</div>
<div class="control">
<button class="button is-light" (click)="showUpdateSettingsModal()">Change priority</button>
</div>
</div>
<div class="field">
<label class="label">Status</label>
@ -40,6 +43,10 @@
<label class="label">Hash</label>
{{ torrent.hash }}
</div>
<div class="field">
<label class="label">Priority</label>
{{ torrent.priority || "" }}
</div>
<div class="field">
<label class="label">Category</label>
{{ torrent.category || "(no category set)" }}
@ -445,3 +452,42 @@
</footer>
</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 downloadRetryId: string;
public isUpdateSettingsModalActive: boolean;
public updateSettingsPriority: number;
public updating: boolean;
constructor(private activatedRoute: ActivatedRoute, private router: Router, private torrentService: TorrentService) {}
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.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
@ -15,6 +16,14 @@ namespace RdtClient.Data.Data
_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)
{
return await _dataContext.Downloads

View file

@ -27,15 +27,12 @@ namespace RdtClient.Data.Data
try
{
if (_torrentCache == null)
{
_torrentCache = await _dataContext.Torrents
.AsNoTracking()
.Include(m => m.Downloads)
.ToListAsync();
}
_torrentCache ??= await _dataContext.Torrents
.AsNoTracking()
.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
{
@ -91,7 +88,8 @@ namespace RdtClient.Data.Data
Int32 downloadMinSize,
String downloadManualFiles,
String fileOrMagnetContents,
Boolean isFile)
Boolean isFile,
Int32? priority)
{
var torrent = new Torrent
{
@ -105,7 +103,8 @@ namespace RdtClient.Data.Data
DownloadMinSize = downloadMinSize,
DownloadManualFiles = downloadManualFiles,
FileOrMagnet = fileOrMagnetContents,
IsFile = isFile
IsFile = isFile,
Priority = priority
};
await _dataContext.Torrents.AddAsync(torrent);
@ -148,6 +147,22 @@ namespace RdtClient.Data.Data
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)
{
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
@ -212,6 +227,22 @@ namespace RdtClient.Data.Data
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)
{
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")
.HasColumnType("INTEGER");
b.Property<int?>("Priority")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset>("RdAdded")
.HasColumnType("TEXT");

View file

@ -28,7 +28,8 @@ namespace RdtClient.Data.Models.Data
public String FileOrMagnet { get; set; }
public Boolean IsFile { get; set; }
public Int32? Priority { get; set; }
public Int32 RetryCount { get; set; }
[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.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using System.Timers;
using Aria2NET;
@ -29,7 +30,12 @@ namespace RdtClient.Service.Services.Downloaders
_uri = uri;
_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();
@ -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)
{
if (_gid == null)

View file

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

View file

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

View file

@ -14,12 +14,14 @@ namespace RdtClient.Service.Services
private readonly Authentication _authentication;
private readonly Settings _settings;
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;
_authentication = authentication;
_torrents = torrents;
_downloads = downloads;
}
public async Task<Boolean> AuthLogin(String userName, String password)
@ -291,7 +293,7 @@ namespace RdtClient.Service.Services
Upspeed = speed
};
if (torrent.Completed.HasValue)
if (torrent.Completed.HasValue && torrent.Downloads.All(m => m.Completed.HasValue))
{
// Indicates completed torrent and not seeding anymore.
result.State = "pausedUP";
@ -329,7 +331,7 @@ namespace RdtClient.Service.Services
return null;
}
foreach (var file in torrent.Files)
foreach (var file in torrent.Files.Where(m => m.Selected))
{
var result = new TorrentFileItem
{
@ -421,18 +423,18 @@ namespace RdtClient.Service.Services
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;
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;
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)
@ -515,5 +517,50 @@ namespace RdtClient.Service.Services
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);
await _torrents.Update();
await _torrents.UpdateRdData();
// Re-get torrents to account for updated info
torrents = await _torrents.Get();
@ -297,6 +297,7 @@ namespace RdtClient.Service.Services
if (download.Link == null)
{
await _downloads.UpdateError(download.DownloadId, "Download Link cannot be null");
await _downloads.UpdateCompleted(download.DownloadId, DateTimeOffset.UtcNow);
continue;
}

View file

@ -83,7 +83,7 @@ namespace RdtClient.Service.Services
if (torrent != null)
{
await Update(torrent);
await UpdateRdData(torrent);
}
return torrent;
@ -106,7 +106,8 @@ namespace RdtClient.Service.Services
TorrentDownloadAction downloadAction,
TorrentFinishedAction finishedAction,
Int32 downloadMinSize,
String downloadManualFiles)
String downloadManualFiles,
Int32? priority)
{
MagnetLink magnet;
@ -121,7 +122,7 @@ namespace RdtClient.Service.Services
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,
@ -129,7 +130,8 @@ namespace RdtClient.Service.Services
TorrentDownloadAction downloadAction,
TorrentFinishedAction finishedAction,
Int32 downloadMinSize,
String downloadManualFiles)
String downloadManualFiles,
Int32? priority)
{
MonoTorrent.Torrent torrent;
@ -146,7 +148,7 @@ namespace RdtClient.Service.Services
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)
@ -373,7 +375,7 @@ namespace RdtClient.Service.Services
return profile;
}
public async Task Update()
public async Task UpdateRdData()
{
await RealDebridUpdateLock.WaitAsync();
@ -392,7 +394,7 @@ namespace RdtClient.Service.Services
continue;
}
await Update(torrent);
await UpdateRdData(torrent);
}
foreach (var torrent in torrents)
@ -457,7 +459,7 @@ namespace RdtClient.Service.Services
{
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
{
@ -466,7 +468,8 @@ namespace RdtClient.Service.Services
torrent.DownloadAction,
torrent.FinishedAction,
torrent.DownloadMinSize,
torrent.DownloadManualFiles);
torrent.DownloadManualFiles,
torrent.Priority);
}
await _torrentData.UpdateRetryCount(newTorrent.TorrentId, newRetryCount);
@ -527,6 +530,18 @@ namespace RdtClient.Service.Services
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)
{
var torrent = await _torrentData.GetById(torrentId);
@ -536,7 +551,7 @@ namespace RdtClient.Service.Services
return null;
}
await Update(torrent);
await UpdateRdData(torrent);
foreach (var download in torrent.Downloads)
{
@ -577,7 +592,8 @@ namespace RdtClient.Service.Services
Int32 downloadMinSize,
String downloadManualFiles,
String fileOrMagnetContents,
Boolean isFile)
Boolean isFile,
Int32? priority)
{
await RealDebridUpdateLock.WaitAsync();
@ -598,9 +614,10 @@ namespace RdtClient.Service.Services
downloadMinSize,
downloadManualFiles,
fileOrMagnetContents,
isFile);
isFile,
priority);
await Update(newTorrent);
await UpdateRdData(newTorrent);
return newTorrent;
}
@ -609,8 +626,13 @@ namespace RdtClient.Service.Services
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,
new JsonSerializerSettings

View file

@ -190,21 +190,49 @@ namespace RdtClient.Web.Controllers
[Authorize]
[Route("torrents/pause")]
[HttpGet]
[HttpPost]
public ActionResult TorrentsPause()
public async Task<ActionResult> TorrentsPause([FromQuery] QBTorrentsHashesRequest request)
{
var hashes = request.Hashes.Split("|");
foreach (var hash in hashes)
{
await _qBittorrent.TorrentPause(hash);
}
return Ok();
}
[Authorize]
[Route("torrents/topPrio")]
[HttpPost]
public async Task<ActionResult> TorrentsPausePost([FromForm] QBTorrentsHashesRequest request)
{
return await TorrentsPause(request);
}
[Authorize]
[Route("torrents/resume")]
[HttpGet]
[HttpPost]
public ActionResult TorrentsResume()
public async Task<ActionResult> TorrentsResume([FromQuery] QBTorrentsHashesRequest request)
{
var hashes = request.Hashes.Split("|");
foreach (var hash in hashes)
{
await _qBittorrent.TorrentResume(hash);
}
return Ok();
}
[Authorize]
[Route("torrents/topPrio")]
[HttpPost]
public async Task<ActionResult> TorrentsResumePost([FromForm] QBTorrentsHashesRequest request)
{
return await TorrentsResume(request);
}
[Authorize]
[Route("torrents/setShareLimits")]
[HttpGet]
@ -248,13 +276,13 @@ namespace RdtClient.Web.Controllers
{
if (url.StartsWith("magnet"))
{
await _qBittorrent.TorrentsAddMagnet(url.Trim(), request.Category);
await _qBittorrent.TorrentsAddMagnet(url.Trim(), request.Category, null);
}
else if (url.StartsWith("http"))
{
var httpClient = new HttpClient();
var result = await httpClient.GetByteArrayAsync(url);
await _qBittorrent.TorrentsAddFile(result, request.Category);
await _qBittorrent.TorrentsAddFile(result, request.Category, null);
}
else
{
@ -279,7 +307,7 @@ namespace RdtClient.Web.Controllers
await file.CopyToAsync(target);
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();
}
[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
@ -393,6 +444,7 @@ namespace RdtClient.Web.Controllers
{
public String Urls { get; set; }
public String Category { get; set; }
public Int32? Priority { get; set; }
}
public class QBTorrentsSetCategoryRequest
@ -410,4 +462,9 @@ namespace RdtClient.Web.Controllers
{
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();
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();
}
@ -105,7 +105,8 @@ namespace RdtClient.Web.Controllers
request.DownloadAction,
request.FinishedAction,
request.DownloadMinSize,
request.DownloadManualFiles);
request.DownloadManualFiles,
request.Priority);
return Ok();
}
@ -171,6 +172,15 @@ namespace RdtClient.Web.Controllers
return Ok();
}
[HttpPut]
[Route("Update")]
public async Task<ActionResult> Update([FromBody] Torrent torrent)
{
await _torrents.Update(torrent);
return Ok();
}
}
public class TorrentControllerUploadFileRequest
@ -180,6 +190,7 @@ namespace RdtClient.Web.Controllers
public TorrentFinishedAction FinishedAction { get; set; }
public Int32 DownloadMinSize { get; set; }
public String DownloadManualFiles { get; set; }
public Int32? Priority { get; set; }
}
public class TorrentControllerUploadMagnetRequest
@ -190,6 +201,7 @@ namespace RdtClient.Web.Controllers
public TorrentFinishedAction FinishedAction { get; set; }
public Int32 DownloadMinSize { get; set; }
public String DownloadManualFiles { get; set; }
public Int32? Priority { get; set; }
}
public class TorrentControllerDeleteRequest