diff --git a/client/src/app/models/download.model.ts b/client/src/app/models/download.model.ts index 663b456..284ac5d 100644 --- a/client/src/app/models/download.model.ts +++ b/client/src/app/models/download.model.ts @@ -10,6 +10,8 @@ export class Download { public status: DownloadStatus; public progress: number; + + public speed: number; } export enum DownloadStatus { diff --git a/client/src/app/models/torrent.model.ts b/client/src/app/models/torrent.model.ts index bd4ac19..50d7ff4 100644 --- a/client/src/app/models/torrent.model.ts +++ b/client/src/app/models/torrent.model.ts @@ -19,7 +19,7 @@ export class Torrent { files: TorrentFile[]; downloads: Download[]; - downloadProgress: number; + activeDownload: Download; } export class TorrentFile { diff --git a/client/src/app/torrent-status.pipe.ts b/client/src/app/torrent-status.pipe.ts index ecee843..dfe4c9e 100644 --- a/client/src/app/torrent-status.pipe.ts +++ b/client/src/app/torrent-status.pipe.ts @@ -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: diff --git a/client/src/app/torrent-table/torrent-table.component.ts b/client/src/app/torrent-table/torrent-table.component.ts index b5e0799..85eac99 100644 --- a/client/src/app/torrent-table/torrent-table.component.ts +++ b/client/src/app/torrent-table/torrent-table.component.ts @@ -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); } diff --git a/server/RdtClient.Data/Data/DataContext.cs b/server/RdtClient.Data/Data/DataContext.cs index 60b19b1..e17c4fc 100644 --- a/server/RdtClient.Data/Data/DataContext.cs +++ b/server/RdtClient.Data/Data/DataContext.cs @@ -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() diff --git a/server/RdtClient.Data/Data/DownloadData.cs b/server/RdtClient.Data/Data/DownloadData.cs index bb59e75..bd5cddf 100644 --- a/server/RdtClient.Data/Data/DownloadData.cs +++ b/server/RdtClient.Data/Data/DownloadData.cs @@ -27,7 +27,10 @@ namespace RdtClient.Data.Data public async Task> Get() { - return await _dataContext.Downloads.ToListAsync(); + return await _dataContext.Downloads + .AsNoTracking() + .Include(m => m.Torrent) + .ToListAsync(); } public async Task Add(Guid torrentId, String link) diff --git a/server/RdtClient.Data/Data/SettingData.cs b/server/RdtClient.Data/Data/SettingData.cs index a6fc13e..f51ba30 100644 --- a/server/RdtClient.Data/Data/SettingData.cs +++ b/server/RdtClient.Data/Data/SettingData.cs @@ -25,7 +25,7 @@ namespace RdtClient.Data.Data public async Task> GetAll() { - return await _dataContext.Settings.ToListAsync(); + return await _dataContext.Settings.AsNoTracking().ToListAsync(); } public async Task Update(IList settings) @@ -47,7 +47,7 @@ namespace RdtClient.Data.Data public async Task Get(String key) { - return await _dataContext.Settings.FirstOrDefaultAsync(m => m.SettingId == key); + return await _dataContext.Settings.AsNoTracking().FirstOrDefaultAsync(m => m.SettingId == key); } } } diff --git a/server/RdtClient.Data/Data/TorrentData.cs b/server/RdtClient.Data/Data/TorrentData.cs index a83c803..e35b65f 100644 --- a/server/RdtClient.Data/Data/TorrentData.cs +++ b/server/RdtClient.Data/Data/TorrentData.cs @@ -30,12 +30,26 @@ namespace RdtClient.Data.Data public async Task> 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 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 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; diff --git a/server/RdtClient.Data/Enums/DownloadStatus.cs b/server/RdtClient.Data/Enums/DownloadStatus.cs index 9e97a1d..45c5d38 100644 --- a/server/RdtClient.Data/Enums/DownloadStatus.cs +++ b/server/RdtClient.Data/Enums/DownloadStatus.cs @@ -4,6 +4,7 @@ { PendingDownload = 0, Downloading, + Unpacking, Finished } } diff --git a/server/RdtClient.Data/Migrations/20200403195110_Initial.Designer.cs b/server/RdtClient.Data/Migrations/20200403195110_Initial.Designer.cs deleted file mode 100644 index 2c0f547..0000000 --- a/server/RdtClient.Data/Migrations/20200403195110_Initial.Designer.cs +++ /dev/null @@ -1,395 +0,0 @@ -// -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("Id") - .HasColumnType("TEXT"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnType("TEXT") - .HasMaxLength(256); - - b.Property("NormalizedName") - .HasColumnType("TEXT") - .HasMaxLength(256); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasName("RoleNameIndex"); - - b.ToTable("AspNetRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("ClaimType") - .HasColumnType("TEXT"); - - b.Property("ClaimValue") - .HasColumnType("TEXT"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccessFailedCount") - .HasColumnType("INTEGER"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("TEXT"); - - b.Property("Email") - .HasColumnType("TEXT") - .HasMaxLength(256); - - b.Property("EmailConfirmed") - .HasColumnType("INTEGER"); - - b.Property("LockoutEnabled") - .HasColumnType("INTEGER"); - - b.Property("LockoutEnd") - .HasColumnType("TEXT"); - - b.Property("NormalizedEmail") - .HasColumnType("TEXT") - .HasMaxLength(256); - - b.Property("NormalizedUserName") - .HasColumnType("TEXT") - .HasMaxLength(256); - - b.Property("PasswordHash") - .HasColumnType("TEXT"); - - b.Property("PhoneNumber") - .HasColumnType("TEXT"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("INTEGER"); - - b.Property("SecurityStamp") - .HasColumnType("TEXT"); - - b.Property("TwoFactorEnabled") - .HasColumnType("INTEGER"); - - b.Property("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", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("INTEGER"); - - b.Property("ClaimType") - .HasColumnType("TEXT"); - - b.Property("ClaimValue") - .HasColumnType("TEXT"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("TEXT"); - - b.Property("ProviderKey") - .HasColumnType("TEXT"); - - b.Property("ProviderDisplayName") - .HasColumnType("TEXT"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("TEXT"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("TEXT"); - - b.Property("RoleId") - .HasColumnType("TEXT"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("TEXT"); - - b.Property("LoginProvider") - .HasColumnType("TEXT"); - - b.Property("Name") - .HasColumnType("TEXT"); - - b.Property("Value") - .HasColumnType("TEXT"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens"); - }); - - modelBuilder.Entity("RdtClient.Data.Models.Data.Download", b => - { - b.Property("DownloadId") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Added") - .HasColumnType("TEXT"); - - b.Property("Link") - .HasColumnType("TEXT"); - - b.Property("Status") - .HasColumnType("INTEGER"); - - b.Property("TorrentId") - .HasColumnType("TEXT"); - - b.HasKey("DownloadId"); - - b.HasIndex("TorrentId"); - - b.ToTable("Downloads"); - }); - - modelBuilder.Entity("RdtClient.Data.Models.Data.Setting", b => - { - b.Property("SettingId") - .HasColumnType("TEXT"); - - b.Property("Type") - .HasColumnType("TEXT"); - - b.Property("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("TorrentId") - .ValueGeneratedOnAdd() - .HasColumnType("TEXT"); - - b.Property("Hash") - .HasColumnType("TEXT"); - - b.Property("RdAdded") - .HasColumnType("TEXT"); - - b.Property("RdEnded") - .HasColumnType("TEXT"); - - b.Property("RdFiles") - .HasColumnType("TEXT"); - - b.Property("RdHost") - .HasColumnType("TEXT"); - - b.Property("RdId") - .HasColumnType("TEXT"); - - b.Property("RdName") - .HasColumnType("TEXT"); - - b.Property("RdProgress") - .HasColumnType("INTEGER"); - - b.Property("RdSeeders") - .HasColumnType("INTEGER"); - - b.Property("RdSize") - .HasColumnType("INTEGER"); - - b.Property("RdSpeed") - .HasColumnType("INTEGER"); - - b.Property("RdSplit") - .HasColumnType("INTEGER"); - - b.Property("RdStatus") - .HasColumnType("TEXT"); - - b.Property("Status") - .HasColumnType("INTEGER"); - - b.HasKey("TorrentId"); - - b.ToTable("Torrents"); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", 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", 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 - } - } -} diff --git a/server/RdtClient.Data/Migrations/20200406172142_Torrents_Add_Category.cs b/server/RdtClient.Data/Migrations/20200406172142_Torrents_Add_Category.cs deleted file mode 100644 index 9fb5a9d..0000000 --- a/server/RdtClient.Data/Migrations/20200406172142_Torrents_Add_Category.cs +++ /dev/null @@ -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( - name: "Category", - table: "Torrents", - nullable: true); - } - - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropColumn( - name: "Category", - table: "Torrents"); - } - } -} diff --git a/server/RdtClient.Data/Migrations/20200406172142_Torrents_Add_Category.Designer.cs b/server/RdtClient.Data/Migrations/20200407174750_Initial.Designer.cs similarity index 96% rename from server/RdtClient.Data/Migrations/20200406172142_Torrents_Add_Category.Designer.cs rename to server/RdtClient.Data/Migrations/20200407174750_Initial.Designer.cs index da8cc46..47df5d9 100644 --- a/server/RdtClient.Data/Migrations/20200406172142_Torrents_Add_Category.Designer.cs +++ b/server/RdtClient.Data/Migrations/20200407174750_Initial.Designer.cs @@ -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 diff --git a/server/RdtClient.Data/Migrations/20200403195110_Initial.cs b/server/RdtClient.Data/Migrations/20200407174750_Initial.cs similarity index 96% rename from server/RdtClient.Data/Migrations/20200403195110_Initial.cs rename to server/RdtClient.Data/Migrations/20200407174750_Initial.cs index c31bd57..f6f2ff1 100644 --- a/server/RdtClient.Data/Migrations/20200403195110_Initial.cs +++ b/server/RdtClient.Data/Migrations/20200407174750_Initial.cs @@ -65,6 +65,7 @@ namespace RdtClient.Data.Migrations { TorrentId = table.Column(nullable: false), Hash = table.Column(nullable: true), + Category = table.Column(nullable: true), Status = table.Column(nullable: false), RdId = table.Column(nullable: true), RdName = table.Column(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", diff --git a/server/RdtClient.Data/Migrations/DataContextModelSnapshot.cs b/server/RdtClient.Data/Migrations/DataContextModelSnapshot.cs index 27cf5d5..0e6007b 100644 --- a/server/RdtClient.Data/Migrations/DataContextModelSnapshot.cs +++ b/server/RdtClient.Data/Migrations/DataContextModelSnapshot.cs @@ -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 diff --git a/server/RdtClient.Data/Models/Data/Download.cs b/server/RdtClient.Data/Models/Data/Download.cs index c94f803..89f7938 100644 --- a/server/RdtClient.Data/Models/Data/Download.cs +++ b/server/RdtClient.Data/Models/Data/Download.cs @@ -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; } } } diff --git a/server/RdtClient.Data/Models/Data/Torrent.cs b/server/RdtClient.Data/Models/Data/Torrent.cs index 1897ea6..0b82aad 100644 --- a/server/RdtClient.Data/Models/Data/Torrent.cs +++ b/server/RdtClient.Data/Models/Data/Torrent.cs @@ -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 Files { diff --git a/server/RdtClient.Service/Services/DownloadManager.cs b/server/RdtClient.Service/Services/DownloadManager.cs index 735365c..0318cc1 100644 --- a/server/RdtClient.Service/Services/DownloadManager.cs +++ b/server/RdtClient.Service/Services/DownloadManager.cs @@ -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 ActiveDownloads = new ConcurrentDictionary(); + public static readonly Dictionary ActiveDownloads = new Dictionary(); 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) diff --git a/server/RdtClient.Service/Services/Scheduler.cs b/server/RdtClient.Service/Services/Scheduler.cs index 98e6767..b575915 100644 --- a/server/RdtClient.Service/Services/Scheduler.cs +++ b/server/RdtClient.Service/Services/Scheduler.cs @@ -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); } diff --git a/server/RdtClient.Service/Services/Torrents.cs b/server/RdtClient.Service/Services/Torrents.cs index c0a7a2f..9f3e530 100644 --- a/server/RdtClient.Service/Services/Torrents.cs +++ b/server/RdtClient.Service/Services/Torrents.cs @@ -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); }