Add a download speed tracker.

This commit is contained in:
Roger Far 2020-04-07 12:08:17 -06:00
parent d7062de42e
commit c96ce980d5
19 changed files with 168 additions and 479 deletions

View file

@ -10,6 +10,8 @@ export class Download {
public status: DownloadStatus;
public progress: number;
public speed: number;
}
export enum DownloadStatus {

View file

@ -19,7 +19,7 @@ export class Torrent {
files: TorrentFile[];
downloads: Download[];
downloadProgress: number;
activeDownload: Download;
}
export class TorrentFile {

View file

@ -10,13 +10,22 @@ export class TorrentStatusPipe implements PipeTransform {
transform(torrent: Torrent): string {
switch (torrent.status) {
case TorrentStatus.RealDebrid:
case TorrentStatus.RealDebrid: {
const speed = this.pipe.transform(torrent.rdSpeed, 'filesize');
return `Downloading from RD (${torrent.rdProgress}% - ${speed}/s)`;
}
case TorrentStatus.WaitingForDownload:
return `Waiting to download`;
case TorrentStatus.Downloading:
return `Downloading (${torrent.downloadProgress}%)`;
case TorrentStatus.Downloading: {
if (torrent.activeDownload != null) {
const speed = this.pipe.transform(
torrent.activeDownload.speed,
'filesize'
);
return `Downloading (${torrent.activeDownload.progress}% - ${speed}/s)`;
}
return `Downloading`;
}
case TorrentStatus.Finished:
return `Finished`;
case TorrentStatus.Error:

View file

@ -7,6 +7,7 @@ import {
} from '@angular/core';
import { Torrent } from '../models/torrent.model';
import { TorrentService } from '../torrent.service';
import { DownloadStatus } from '../models/download.model';
@Component({
selector: 'app-torrent-table',
@ -26,6 +27,15 @@ export class TorrentTableComponent implements OnInit, OnDestroy {
this.timer = setInterval(() => {
this.torrentService.getList().subscribe((result) => {
this.torrents = result;
this.torrents.forEach((torrent) => {
const activeDownloads = torrent.downloads.filter(
(m) => m.status === DownloadStatus.Downloading
);
if (activeDownloads.length > 0) {
torrent.activeDownload = activeDownloads[0];
}
});
});
}, 1000);
}

View file

@ -1,4 +1,5 @@
using System;
using System.Linq;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using RdtClient.Data.Models.Data;
@ -50,6 +51,15 @@ namespace RdtClient.Data.Data
Type = "Int32",
Value = "10"
});
var cascadeFKs = builder.Model.GetEntityTypes()
.SelectMany(t => t.GetForeignKeys())
.Where(fk => !fk.IsOwnership && fk.DeleteBehavior == DeleteBehavior.Cascade);
foreach (var fk in cascadeFKs)
{
fk.DeleteBehavior = DeleteBehavior.Restrict;
}
}
public void Migrate()

View file

@ -27,7 +27,10 @@ namespace RdtClient.Data.Data
public async Task<IList<Download>> Get()
{
return await _dataContext.Downloads.ToListAsync();
return await _dataContext.Downloads
.AsNoTracking()
.Include(m => m.Torrent)
.ToListAsync();
}
public async Task<Download> Add(Guid torrentId, String link)

View file

@ -25,7 +25,7 @@ namespace RdtClient.Data.Data
public async Task<IList<Setting>> GetAll()
{
return await _dataContext.Settings.ToListAsync();
return await _dataContext.Settings.AsNoTracking().ToListAsync();
}
public async Task Update(IList<Setting> settings)
@ -47,7 +47,7 @@ namespace RdtClient.Data.Data
public async Task<Setting> Get(String key)
{
return await _dataContext.Settings.FirstOrDefaultAsync(m => m.SettingId == key);
return await _dataContext.Settings.AsNoTracking().FirstOrDefaultAsync(m => m.SettingId == key);
}
}
}

View file

@ -30,12 +30,26 @@ namespace RdtClient.Data.Data
public async Task<IList<Torrent>> Get()
{
return await _dataContext.Torrents.ToListAsync();
var results = await _dataContext.Torrents
.AsNoTracking()
.Include(m => m.Downloads)
.ToListAsync();
foreach (var torrent in results)
{
foreach (var file in torrent.Downloads)
{
file.Torrent = null;
}
}
return results;
}
public async Task<Torrent> GetById(Guid id)
{
var results = await _dataContext.Torrents
.AsNoTracking()
.Include(m => m.Downloads)
.FirstOrDefaultAsync(m => m.TorrentId == id);
@ -50,6 +64,7 @@ namespace RdtClient.Data.Data
public async Task<Torrent> GetByHash(String hash)
{
var results = await _dataContext.Torrents
.AsNoTracking()
.Include(m => m.Downloads)
.FirstOrDefaultAsync(m => m.Hash == hash);
@ -82,6 +97,8 @@ namespace RdtClient.Data.Data
{
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrent.TorrentId);
dbTorrent.Status = torrent.Status;
dbTorrent.RdName = torrent.RdName;
dbTorrent.RdSize = torrent.RdSize;
dbTorrent.RdHost = torrent.RdHost;

View file

@ -4,6 +4,7 @@
{
PendingDownload = 0,
Downloading,
Unpacking,
Finished
}
}

View file

@ -1,395 +0,0 @@
// <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("20200403195110_Initial")]
partial class Initial
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.1.3");
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("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")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed")
.HasColumnType("INTEGER");
b.Property<bool>("LockoutEnabled")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("TEXT");
b.Property<string>("NormalizedEmail")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasColumnType("TEXT")
.HasMaxLength(256);
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")
.HasColumnType("TEXT")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("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<string>("Link")
.HasColumnType("TEXT");
b.Property<int>("Status")
.HasColumnType("INTEGER");
b.Property<Guid>("TorrentId")
.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");
b.HasData(
new
{
SettingId = "RealDebridApiKey",
Type = "String",
Value = ""
},
new
{
SettingId = "DownloadFolder",
Type = "String",
Value = "C:\\Downloads"
},
new
{
SettingId = "SpeedLimit",
Type = "Int32",
Value = "0"
},
new
{
SettingId = "SegmentsLimit",
Type = "Int32",
Value = "10"
});
});
modelBuilder.Entity("RdtClient.Data.Models.Data.Torrent", b =>
{
b.Property<Guid>("TorrentId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("Hash")
.HasColumnType("TEXT");
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<string>("RdStatus")
.HasColumnType("TEXT");
b.Property<int>("Status")
.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.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("RdtClient.Data.Models.Data.Download", b =>
{
b.HasOne("RdtClient.Data.Models.Data.Torrent", "Torrent")
.WithMany("Downloads")
.HasForeignKey("TorrentId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View file

@ -1,22 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace RdtClient.Data.Migrations
{
public partial class Torrents_Add_Category : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Category",
table: "Torrents",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Category",
table: "Torrents");
}
}
}

View file

@ -9,8 +9,8 @@ using RdtClient.Data.Data;
namespace RdtClient.Data.Migrations
{
[DbContext(typeof(DataContext))]
[Migration("20200406172142_Torrents_Add_Category")]
partial class Torrents_Add_Category
[Migration("20200407174750_Initial")]
partial class Initial
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
@ -332,7 +332,7 @@ namespace RdtClient.Data.Migrations
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
});
@ -341,7 +341,7 @@ namespace RdtClient.Data.Migrations
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
});
@ -350,7 +350,7 @@ namespace RdtClient.Data.Migrations
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
});
@ -359,13 +359,13 @@ namespace RdtClient.Data.Migrations
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
});
@ -374,7 +374,7 @@ namespace RdtClient.Data.Migrations
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
});
@ -383,7 +383,7 @@ namespace RdtClient.Data.Migrations
b.HasOne("RdtClient.Data.Models.Data.Torrent", "Torrent")
.WithMany("Downloads")
.HasForeignKey("TorrentId")
.OnDelete(DeleteBehavior.Cascade)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
});
#pragma warning restore 612, 618

View file

@ -65,6 +65,7 @@ namespace RdtClient.Data.Migrations
{
TorrentId = table.Column<Guid>(nullable: false),
Hash = table.Column<string>(nullable: true),
Category = table.Column<string>(nullable: true),
Status = table.Column<int>(nullable: false),
RdId = table.Column<string>(nullable: true),
RdName = table.Column<string>(nullable: true),
@ -102,7 +103,7 @@ namespace RdtClient.Data.Migrations
column: x => x.RoleId,
principalTable: "AspNetRoles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
@ -123,7 +124,7 @@ namespace RdtClient.Data.Migrations
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
@ -143,7 +144,7 @@ namespace RdtClient.Data.Migrations
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
@ -161,13 +162,13 @@ namespace RdtClient.Data.Migrations
column: x => x.RoleId,
principalTable: "AspNetRoles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_AspNetUserRoles_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
@ -187,7 +188,7 @@ namespace RdtClient.Data.Migrations
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
@ -208,7 +209,7 @@ namespace RdtClient.Data.Migrations
column: x => x.TorrentId,
principalTable: "Torrents",
principalColumn: "TorrentId",
onDelete: ReferentialAction.Cascade);
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.InsertData(
@ -224,7 +225,7 @@ namespace RdtClient.Data.Migrations
migrationBuilder.InsertData(
table: "Settings",
columns: new[] { "SettingId", "Type", "Value" },
values: new object[] { "DownloadLimit", "Int32", "1" });
values: new object[] { "DownloadLimit", "Int32", "10" });
migrationBuilder.CreateIndex(
name: "IX_AspNetRoleClaims_RoleId",

View file

@ -330,7 +330,7 @@ namespace RdtClient.Data.Migrations
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
});
@ -339,7 +339,7 @@ namespace RdtClient.Data.Migrations
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
});
@ -348,7 +348,7 @@ namespace RdtClient.Data.Migrations
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
});
@ -357,13 +357,13 @@ namespace RdtClient.Data.Migrations
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
});
@ -372,7 +372,7 @@ namespace RdtClient.Data.Migrations
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
});
@ -381,7 +381,7 @@ namespace RdtClient.Data.Migrations
b.HasOne("RdtClient.Data.Models.Data.Torrent", "Torrent")
.WithMany("Downloads")
.HasForeignKey("TorrentId")
.OnDelete(DeleteBehavior.Cascade)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
});
#pragma warning restore 612, 618

View file

@ -23,5 +23,14 @@ namespace RdtClient.Data.Models.Data
[NotMapped]
public Int32 Progress { get; set; }
[NotMapped]
public Int64 Speed { get; set; }
[NotMapped]
public DateTime NextUpdate { get; set; }
[NotMapped]
public Int64 BytesLastUpdate { get; set; }
}
}

View file

@ -35,9 +35,6 @@ namespace RdtClient.Data.Models.Data
public Int64? RdSeeders { get; set; }
public String RdFiles { get; set; }
[NotMapped]
public Int32 DownloadProgress { get; set; }
[NotMapped]
public IList<TorrentFile> Files
{

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
@ -15,7 +16,7 @@ namespace RdtClient.Service.Services
{
public static class DownloadManager
{
public static readonly ConcurrentDictionary<Guid, Download> ActiveDownloads = new ConcurrentDictionary<Guid, Download>();
public static readonly Dictionary<Guid, Download> ActiveDownloads = new Dictionary<Guid, Download>();
static DownloadManager()
{
@ -33,11 +34,21 @@ namespace RdtClient.Service.Services
return;
}
download.Progress = 0;
download.BytesLastUpdate = 0;
download.NextUpdate = DateTime.UtcNow.AddSeconds(1);
download.Speed = 0;
var fileUrl = download.Link;
var uri = new Uri(fileUrl);
var filePath = Path.Combine(destinationFolderPath, uri.Segments.Last());
if (!Directory.Exists(destinationFolderPath))
{
Directory.CreateDirectory(destinationFolderPath);
}
var webRequest = WebRequest.Create(fileUrl);
webRequest.Method = "HEAD";
Int64 responseLength;
@ -56,7 +67,6 @@ namespace RdtClient.Service.Services
{
await using var stream = response.GetResponseStream();
await using var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Write);
var buffer = new Byte[4096];
while (fileStream.Length < response.ContentLength)
@ -67,8 +77,17 @@ namespace RdtClient.Service.Services
{
fileStream.Write(buffer, 0, read);
download.Progress = (Int32) (fileStream.Length * 100 / responseLength);
ActiveDownloads.TryAdd(download.DownloadId, download);
ActiveDownloads[download.DownloadId].Progress = (Int32) (fileStream.Length * 100 / responseLength);
if (DateTime.UtcNow > ActiveDownloads[download.DownloadId]
.NextUpdate)
{
ActiveDownloads[download.DownloadId].Speed = fileStream.Length - ActiveDownloads[download.DownloadId].BytesLastUpdate;
ActiveDownloads[download.DownloadId].NextUpdate = DateTime.UtcNow.AddSeconds(1);
ActiveDownloads[download.DownloadId].BytesLastUpdate = fileStream.Length;
Debug.WriteLine($"{fileStream.Length}/{responseLength} {ActiveDownloads[download.DownloadId].Speed}");
}
}
else
{
@ -77,26 +96,48 @@ namespace RdtClient.Service.Services
}
}
ActiveDownloads[download.DownloadId].Speed = 0;
try
{
await using Stream stream = File.OpenRead(filePath);
var reader = ReaderFactory.Open(stream);
while (reader.MoveToNextEntry())
if (filePath.EndsWith(".rar"))
{
if (!reader.Entry.IsDirectory)
await UpdateStatus(download.DownloadId, DownloadStatus.Unpacking, TorrentStatus.Downloading);
await using (Stream stream = File.OpenRead(filePath))
{
Console.WriteLine(reader.Entry.Key);
reader.WriteEntryToDirectory(destinationFolderPath,
new ExtractionOptions
{
ExtractFullPath = true,
Overwrite = true
});
var reader = ReaderFactory.Open(stream);
while (reader.MoveToNextEntry())
{
if (reader.Entry.IsDirectory)
{
continue;
}
reader.WriteEntryToDirectory(destinationFolderPath,
new ExtractionOptions
{
ExtractFullPath = true,
Overwrite = true
});
}
}
var retryCount = 0;
while (File.Exists(filePath) && retryCount < 10)
{
retryCount++;
try
{
File.Delete(filePath);
}
catch
{
await Task.Delay(1000);
}
}
}
File.Delete(filePath);
}
catch
{
@ -105,7 +146,7 @@ namespace RdtClient.Service.Services
await UpdateStatus(download.DownloadId, DownloadStatus.Finished, TorrentStatus.Finished);
ActiveDownloads.TryRemove(download.DownloadId, out _);
ActiveDownloads.Remove(download.DownloadId);
}
private static async Task UpdateStatus(Guid downloadId, DownloadStatus downloadStatus, TorrentStatus torrentStatus)

View file

@ -1,4 +1,5 @@
using System.Linq;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Hangfire;
using RdtClient.Data.Enums;
@ -52,8 +53,10 @@ namespace RdtClient.Service.Services
return;
}
var folderPath = Path.Combine(destinationFolderPath, download.Torrent.RdName);
download.Torrent = null;
BackgroundJob.Enqueue(() => DownloadManager.Download(download, destinationFolderPath));
BackgroundJob.Enqueue(() => DownloadManager.Download(download, folderPath));
await Task.Delay(1000);
}

View file

@ -80,12 +80,13 @@ namespace RdtClient.Service.Services
foreach (var torrent in torrents)
{
var downloads = DownloadManager.ActiveDownloads.Where(m => m.Value.TorrentId == torrent.TorrentId)
.ToList();
if (torrent.Files.Count > 0)
foreach (var download in torrent.Downloads)
{
torrent.DownloadProgress = downloads.Sum(m => m.Value.Progress) / torrent.Files.Count;
if (DownloadManager.ActiveDownloads.TryGetValue(download.DownloadId, out var activeDownload))
{
download.Speed = activeDownload.Speed;
download.Progress = activeDownload.Progress;
}
}
}
@ -155,6 +156,7 @@ namespace RdtClient.Service.Services
if (rdTorrent == null)
{
await _downloads.DeleteForTorrent(torrent.TorrentId);
await _torrentData.Delete(torrent.TorrentId);
}
}
@ -203,6 +205,7 @@ namespace RdtClient.Service.Services
if (torrent != null)
{
await _downloads.DeleteForTorrent(torrent.TorrentId);
await _torrentData.Delete(id);
await RdNetClient.TorrentDelete(torrent.RdId);
}