diff --git a/README.md b/README.md
index b693b30..59b00ab 100644
--- a/README.md
+++ b/README.md
@@ -105,6 +105,15 @@ It has the following options:
- Parallel connections per download: This number indicates how many threads/connections/parts/chunks it will use per download. This can increase speed, recommended is no more than 8.
- Download speed (in MB/s): This number indicates the speed in MB/s per download. If you set this to 10 and `Maximum parallel downloads` to 2, you can download with a maximum of 20MB/s.
+#### Aria2c downloader
+
+This will use an external Aria2c downloader client. You will need to install this client yourself on your host, it is not included in the docker image.
+
+It has the following options:
+
+- Url: The full URL to your Aria2c service. This must end in /jsonrpc. A standard path is `http://192.168.10.2:6800/jsonrpc`.
+- Secret: Optional secret to connecto to your Aria2c service.
+
### Troubleshooting
- If you forgot your logins simply delete the `rdtclient.db` and restart the service.
diff --git a/client/src/app/settings/settings.component.html b/client/src/app/settings/settings.component.html
index deccb9f..6b1d404 100644
--- a/client/src/app/settings/settings.component.html
+++ b/client/src/app/settings/settings.component.html
@@ -75,6 +75,7 @@
Simple Downloader
Multi Part Downloader
+ Aria2c
diff --git a/client/src/app/settings/settings.component.ts b/client/src/app/settings/settings.component.ts
index dcf0c73..31d7fbb 100644
--- a/client/src/app/settings/settings.component.ts
+++ b/client/src/app/settings/settings.component.ts
@@ -36,6 +36,9 @@ export class SettingsComponent implements OnInit {
public settingOnlyDownloadAvailableFiles: boolean;
public settingProxyServer: string;
+ public aria2cUrl: string;
+ public aria2cSecret: string;
+
constructor(private settingsService: SettingsService) {}
ngOnInit(): void {
@@ -61,6 +64,8 @@ export class SettingsComponent implements OnInit {
this.settingMinFileSize = parseInt(this.getSetting(results, 'MinFileSize'), 10);
this.settingOnlyDownloadAvailableFiles = this.getSetting(results, 'OnlyDownloadAvailableFiles') === '1';
this.settingProxyServer = this.getSetting(results, 'ProxyServer');
+ this.aria2cUrl = this.getSetting(results, 'Aria2cUrl');
+ this.aria2cSecret = this.getSetting(results, 'Aria2cSecret');
},
(err) => {
this.error = err.error;
@@ -125,6 +130,14 @@ export class SettingsComponent implements OnInit {
settingId: 'ProxyServer',
value: this.settingProxyServer,
},
+ {
+ settingId: 'Aria2cUrl',
+ value: this.aria2cUrl,
+ },
+ {
+ settingId: 'Aria2cSecret',
+ value: this.aria2cSecret,
+ },
];
this.settingsService.update(settings).subscribe(
diff --git a/server/RdtClient.Data/Data/DataContext.cs b/server/RdtClient.Data/Data/DataContext.cs
index b79e210..a920088 100644
--- a/server/RdtClient.Data/Data/DataContext.cs
+++ b/server/RdtClient.Data/Data/DataContext.cs
@@ -135,6 +135,18 @@ namespace RdtClient.Data.Data
SettingId = "Categories",
Type = "String",
Value = ""
+ },
+ new Setting
+ {
+ SettingId = "Aria2cUrl",
+ Type = "String",
+ Value = "http://127.0.0.1:6800/jsonrpc"
+ },
+ new Setting
+ {
+ SettingId = "Aria2cSecret",
+ Type = "String",
+ Value = ""
}
};
diff --git a/server/RdtClient.Data/Data/DownloadData.cs b/server/RdtClient.Data/Data/DownloadData.cs
index f5e423d..475d491 100644
--- a/server/RdtClient.Data/Data/DownloadData.cs
+++ b/server/RdtClient.Data/Data/DownloadData.cs
@@ -164,6 +164,16 @@ namespace RdtClient.Data.Data
await TorrentData.VoidCache();
}
+ public async Task UpdateRemoteId(Guid downloadId, String remoteId)
+ {
+ var dbDownload = await _dataContext.Downloads
+ .FirstOrDefaultAsync(m => m.DownloadId == downloadId);
+
+ dbDownload.RemoteId = remoteId;
+
+ await _dataContext.SaveChangesAsync();
+ }
+
public async Task DeleteForTorrent(Guid torrentId)
{
var downloads = await _dataContext.Downloads
diff --git a/server/RdtClient.Data/Data/SettingData.cs b/server/RdtClient.Data/Data/SettingData.cs
index f12054b..f710e98 100644
--- a/server/RdtClient.Data/Data/SettingData.cs
+++ b/server/RdtClient.Data/Data/SettingData.cs
@@ -44,7 +44,9 @@ namespace RdtClient.Data.Data
DownloadMaxSpeed = GetInt32("DownloadMaxSpeed"),
ProxyServer = GetString("ProxyServer"),
LogLevel = GetString("LogLevel"),
- Categories = GetString("Categories")
+ Categories = GetString("Categories"),
+ Aria2cUrl = GetString("Aria2cUrl"),
+ Aria2cSecret = GetString("Aria2cSecret"),
};
}
diff --git a/server/RdtClient.Data/Migrations/20211009221133_Downloads_Add_RemoteId.Designer.cs b/server/RdtClient.Data/Migrations/20211009221133_Downloads_Add_RemoteId.Designer.cs
new file mode 100644
index 0000000..62b0424
--- /dev/null
+++ b/server/RdtClient.Data/Migrations/20211009221133_Downloads_Add_RemoteId.Designer.cs
@@ -0,0 +1,436 @@
+//
+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("20211009221133_Downloads_Add_RemoteId")]
+ partial class Downloads_Add_RemoteId
+ {
+ 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
("Id")
+ .HasColumnType("TEXT");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedName")
+ .IsUnique()
+ .HasDatabaseName("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")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("EmailConfirmed")
+ .HasColumnType("INTEGER");
+
+ b.Property("LockoutEnabled")
+ .HasColumnType("INTEGER");
+
+ b.Property("LockoutEnd")
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedEmail")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedUserName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ 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")
+ .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", 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("Completed")
+ .HasColumnType("TEXT");
+
+ b.Property("DownloadFinished")
+ .HasColumnType("TEXT");
+
+ b.Property("DownloadQueued")
+ .HasColumnType("TEXT");
+
+ b.Property("DownloadStarted")
+ .HasColumnType("TEXT");
+
+ b.Property("Error")
+ .HasColumnType("TEXT");
+
+ b.Property("Link")
+ .HasColumnType("TEXT");
+
+ b.Property("Path")
+ .HasColumnType("TEXT");
+
+ b.Property("RemoteId")
+ .HasColumnType("TEXT");
+
+ b.Property("RetryCount")
+ .HasColumnType("INTEGER");
+
+ b.Property("TorrentId")
+ .HasColumnType("TEXT");
+
+ b.Property("UnpackingFinished")
+ .HasColumnType("TEXT");
+
+ b.Property("UnpackingQueued")
+ .HasColumnType("TEXT");
+
+ b.Property("UnpackingStarted")
+ .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");
+ });
+
+ modelBuilder.Entity("RdtClient.Data.Models.Data.Torrent", b =>
+ {
+ b.Property("TorrentId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT");
+
+ b.Property("Added")
+ .HasColumnType("TEXT");
+
+ b.Property("Category")
+ .HasColumnType("TEXT");
+
+ b.Property("Completed")
+ .HasColumnType("TEXT");
+
+ b.Property("DownloadAction")
+ .HasColumnType("INTEGER");
+
+ b.Property("DownloadManualFiles")
+ .HasColumnType("TEXT");
+
+ b.Property("DownloadMinSize")
+ .HasColumnType("INTEGER");
+
+ b.Property("FileOrMagnet")
+ .HasColumnType("TEXT");
+
+ b.Property("FilesSelected")
+ .HasColumnType("TEXT");
+
+ b.Property("FinishedAction")
+ .HasColumnType("INTEGER");
+
+ b.Property("Hash")
+ .HasColumnType("TEXT");
+
+ b.Property("IsFile")
+ .HasColumnType("INTEGER");
+
+ 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("INTEGER");
+
+ b.Property("RdStatusRaw")
+ .HasColumnType("TEXT");
+
+ 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.Restrict)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", 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", 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
+ }
+ }
+}
diff --git a/server/RdtClient.Data/Migrations/20211009221133_Downloads_Add_RemoteId.cs b/server/RdtClient.Data/Migrations/20211009221133_Downloads_Add_RemoteId.cs
new file mode 100644
index 0000000..bae4101
--- /dev/null
+++ b/server/RdtClient.Data/Migrations/20211009221133_Downloads_Add_RemoteId.cs
@@ -0,0 +1,23 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace RdtClient.Data.Migrations
+{
+ public partial class Downloads_Add_RemoteId : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "RemoteId",
+ table: "Downloads",
+ type: "TEXT",
+ nullable: true);
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "RemoteId",
+ table: "Downloads");
+ }
+ }
+}
diff --git a/server/RdtClient.Data/Migrations/DataContextModelSnapshot.cs b/server/RdtClient.Data/Migrations/DataContextModelSnapshot.cs
index 9cc3b17..f4cf51b 100644
--- a/server/RdtClient.Data/Migrations/DataContextModelSnapshot.cs
+++ b/server/RdtClient.Data/Migrations/DataContextModelSnapshot.cs
@@ -14,7 +14,7 @@ namespace RdtClient.Data.Migrations
{
#pragma warning disable 612, 618
modelBuilder
- .HasAnnotation("ProductVersion", "5.0.8");
+ .HasAnnotation("ProductVersion", "5.0.10");
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
@@ -238,6 +238,9 @@ namespace RdtClient.Data.Migrations
b.Property("Path")
.HasColumnType("TEXT");
+ b.Property("RemoteId")
+ .HasColumnType("TEXT");
+
b.Property("RetryCount")
.HasColumnType("INTEGER");
diff --git a/server/RdtClient.Data/Models/Data/Download.cs b/server/RdtClient.Data/Models/Data/Download.cs
index 9efed0e..31a3a78 100644
--- a/server/RdtClient.Data/Models/Data/Download.cs
+++ b/server/RdtClient.Data/Models/Data/Download.cs
@@ -35,6 +35,8 @@ namespace RdtClient.Data.Models.Data
public String Error { get; set; }
+ public String RemoteId { get; set; }
+
[ForeignKey("TorrentId")]
public Torrent Torrent { get; set; }
diff --git a/server/RdtClient.Data/Models/Internal/DbSettings.cs b/server/RdtClient.Data/Models/Internal/DbSettings.cs
index d7e7d2e..0c89239 100644
--- a/server/RdtClient.Data/Models/Internal/DbSettings.cs
+++ b/server/RdtClient.Data/Models/Internal/DbSettings.cs
@@ -18,5 +18,7 @@ namespace RdtClient.Data.Models.Internal
public String ProxyServer { get; set; }
public String LogLevel { get; set; }
public String Categories { get; set; }
+ public String Aria2cUrl { get; set; }
+ public String Aria2cSecret { get; set; }
}
}
diff --git a/server/RdtClient.Service/RdtClient.Service.csproj b/server/RdtClient.Service/RdtClient.Service.csproj
index 3c9eee1..9b441ed 100644
--- a/server/RdtClient.Service/RdtClient.Service.csproj
+++ b/server/RdtClient.Service/RdtClient.Service.csproj
@@ -6,6 +6,7 @@
+
diff --git a/server/RdtClient.Service/Services/DownloadClient.cs b/server/RdtClient.Service/Services/DownloadClient.cs
index 83a99b1..646b0df 100644
--- a/server/RdtClient.Service/Services/DownloadClient.cs
+++ b/server/RdtClient.Service/Services/DownloadClient.cs
@@ -1,11 +1,10 @@
using System;
using System.IO;
-using System.Net;
using System.Threading.Tasks;
-using Downloader;
using RdtClient.Data.Models.Data;
using RdtClient.Data.Models.Internal;
using RdtClient.Service.Helpers;
+using RdtClient.Service.Services.Downloaders;
namespace RdtClient.Service.Services
{
@@ -16,8 +15,7 @@ namespace RdtClient.Service.Services
private readonly Download _download;
private readonly Torrent _torrent;
- private DownloadService _downloader;
- private SimpleDownloader _simpleDownloader;
+ private IDownloader _downloader;
public DownloadClient(Download download, Torrent torrent, String destinationPath)
{
@@ -34,7 +32,7 @@ namespace RdtClient.Service.Services
public Int64 BytesTotal { get; private set; }
public Int64 BytesDone { get; private set; }
- public async Task Start(DbSettings settings)
+ public async Task Start(DbSettings settings)
{
BytesDone = 0;
BytesTotal = 0;
@@ -54,159 +52,41 @@ namespace RdtClient.Service.Services
File.Delete(filePath);
}
- var uri = new Uri(_download.Link);
+ _downloader = settings.DownloadClient switch
+ {
+ "Simple" => new SimpleDownloader(_download.Link, filePath),
+ "MultiPart" => new MultiDownloader(_download.Link, filePath, settings),
+ "Aria2c" => new Aria2cDownloader(_download.RemoteId, _download.Link, filePath, settings),
+ _ => throw new Exception($"Unknown download client {settings.DownloadClient}")
+ };
-#pragma warning disable 4014
- Task.Run(async delegate
-#pragma warning restore 4014
- {
- switch (settings.DownloadClient)
- {
- case "Simple":
- await DownloadSimple(uri, filePath);
- break;
- case "MultiPart":
- await DownloadMultiPart(filePath, settings);
- break;
- default:
- throw new Exception($"Unknown download client {settings.DownloadClient}");
- }
- });
+ _downloader.DownloadComplete += (_, args) =>
+ {
+ Finished = true;
+ Error = args.Error;
+ };
+
+ _downloader.DownloadProgress += (_, args) =>
+ {
+ Speed = args.Speed;
+ BytesDone = args.BytesDone;
+ BytesTotal = args.BytesTotal;
+ };
+
+ return await _downloader.Download();
}
catch (Exception ex)
{
Error = $"An unexpected error occurred preparing download {_download.Link} for torrent {_torrent.RdName}: {ex.Message}";
Finished = true;
+
+ return null;
}
}
public void Cancel()
{
- _downloader?.CancelAsync();
- _simpleDownloader?.Cancel();
- }
-
- private async Task DownloadSimple(Uri uri, String filePath)
- {
- try
- {
- _simpleDownloader = new SimpleDownloader();
-
- _simpleDownloader.DownloadProgressChanged += (_, args) =>
- {
- Speed = (Int64) args.BytesPerSecondSpeed;
- BytesDone = args.ReceivedBytesSize;
- BytesTotal = args.TotalBytesToReceive;
- };
-
- _simpleDownloader.DownloadFileCompleted += (_, args) =>
- {
- if (args.Cancelled)
- {
- Error = $"The download was cancelled";
- }
- else if (args.Error != null)
- {
- Error = args.Error.Message;
- }
-
- Finished = true;
- };
-
- Speed = 0;
- BytesDone = 0;
- BytesTotal = 0;
-
- await _simpleDownloader.Download(uri, filePath);
- }
- catch (Exception ex)
- {
- Error = $"An unexpected error occurred downloading {_download.Link} for torrent {_torrent.RdName}: {ex.Message}";
- Finished = true;
- }
- }
-
- private async Task DownloadMultiPart(String filePath, DbSettings settings)
- {
- try
- {
- var settingTempPath = settings.TempPath;
- if (String.IsNullOrWhiteSpace(settingTempPath))
- {
- settingTempPath = Path.GetTempPath();
- }
-
- var settingDownloadChunkCount = settings.DownloadChunkCount;
- if (settingDownloadChunkCount <= 0)
- {
- settingDownloadChunkCount = 1;
- }
-
- var settingDownloadMaxSpeed = settings.DownloadMaxSpeed;
- if (settingDownloadMaxSpeed <= 0)
- {
- settingDownloadMaxSpeed = 0;
- }
- settingDownloadMaxSpeed = settingDownloadMaxSpeed * 1024 * 1024;
-
- var settingProxyServer = settings.ProxyServer;
-
- var downloadOpt = new DownloadConfiguration
- {
- MaxTryAgainOnFailover = Int32.MaxValue,
- ParallelDownload = settingDownloadChunkCount > 1,
- ChunkCount = settingDownloadChunkCount,
- Timeout = 1000,
- OnTheFlyDownload = false,
- BufferBlockSize = 1024 * 8,
- MaximumBytesPerSecond = settingDownloadMaxSpeed,
- TempDirectory = settingTempPath,
- RequestConfiguration =
- {
- Accept = "*/*",
- UserAgent = $"rdt-client",
- ProtocolVersion = HttpVersion.Version11,
- AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
- KeepAlive = true,
- UseDefaultCredentials = false
- }
- };
-
- if (!String.IsNullOrWhiteSpace(settingProxyServer))
- {
- downloadOpt.RequestConfiguration.Proxy = new WebProxy(new Uri(settingProxyServer), false);
- }
-
- _downloader = new DownloadService(downloadOpt);
-
- _downloader.DownloadProgressChanged += (_, args) =>
- {
- Speed = (Int64) args.BytesPerSecondSpeed;
- BytesDone = args.ReceivedBytesSize;
- BytesTotal = args.TotalBytesToReceive;
- };
-
- _downloader.DownloadFileCompleted += (_, args) =>
- {
- if (args.Cancelled)
- {
- Error = $"The download was cancelled";
- }
- else if (args.Error != null)
- {
- Error = args.Error.Message;
- }
-
- Finished = true;
- };
-
- await _downloader.DownloadFileTaskAsync(_download.Link, filePath);
- }
- catch (Exception ex)
- {
- Error = $"An unexpected error occurred downloading {_download.Link} for torrent {_torrent.RdName}: {ex.Message}";
- Finished = true;
- }
+ _downloader?.Cancel();
}
}
}
diff --git a/server/RdtClient.Service/Services/Downloaders/Aria2cDownloader.cs b/server/RdtClient.Service/Services/Downloaders/Aria2cDownloader.cs
new file mode 100644
index 0000000..a909306
--- /dev/null
+++ b/server/RdtClient.Service/Services/Downloaders/Aria2cDownloader.cs
@@ -0,0 +1,146 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Threading.Tasks;
+using System.Timers;
+using Aria2NET;
+using RdtClient.Data.Models.Internal;
+
+namespace RdtClient.Service.Services.Downloaders
+{
+ public class Aria2cDownloader : IDownloader
+ {
+ public event EventHandler DownloadComplete;
+ public event EventHandler DownloadProgress;
+
+ private readonly String _uri;
+ private readonly String _filePath;
+
+ private readonly Aria2NetClient _aria2NetClient;
+ private readonly Timer _timer;
+
+ private String _gid;
+
+ public Aria2cDownloader(String gid, String uri, String filePath, DbSettings settings)
+ {
+ _gid = gid;
+ _uri = uri;
+ _filePath = filePath;
+
+ _aria2NetClient = new Aria2NetClient(settings.Aria2cUrl, settings.Aria2cSecret);
+
+ _timer = new Timer();
+
+ _timer.Elapsed += OnTimedEvent;
+
+ _timer.Interval = 1000;
+ _timer.Enabled = false;
+ }
+
+ public async Task Download()
+ {
+ var path = Path.GetDirectoryName(_filePath);
+ var fileName = Path.GetFileName(_filePath);
+
+ if (_gid != null)
+ {
+ try
+ {
+ await _aria2NetClient.TellStatus(_gid);
+ }
+ catch
+ {
+ _gid = null;
+ }
+ }
+
+ _gid ??= await _aria2NetClient.AddUri(new List
+ {
+ _uri
+ },
+ new Dictionary
+ {
+ {
+ "dir", path
+ },
+ {
+ "out", fileName
+ }
+ });
+
+ _timer.Start();
+
+ return _gid;
+ }
+
+ public async Task Cancel()
+ {
+ _timer.Stop();
+
+ if (String.IsNullOrWhiteSpace(_gid))
+ {
+ return;
+ }
+
+ try
+ {
+ await _aria2NetClient.ForceRemove(_gid);
+ }
+ catch
+ {
+ // ignored
+ }
+
+ try
+ {
+ await _aria2NetClient.RemoveDownloadResult(_gid);
+ }
+ catch
+ {
+ // ignored
+ }
+ }
+
+ private async void OnTimedEvent(Object source, ElapsedEventArgs e)
+ {
+ if (_gid == null)
+ {
+ return;
+ }
+
+ try
+ {
+ var status = await _aria2NetClient.TellStatus(_gid);
+
+ if (!String.IsNullOrWhiteSpace(status.ErrorMessage) || status.Status == "error")
+ {
+ await Cancel();
+ DownloadComplete?.Invoke(this, new DownloadCompleteEventArgs
+ {
+ Error = $"{status.ErrorCode}: {status.ErrorMessage}"
+ });
+ return;
+ }
+
+ if (status.Status == "complete" || status.Status == "removed")
+ {
+ await Cancel();
+ DownloadComplete?.Invoke(this, new DownloadCompleteEventArgs());
+ return;
+ }
+
+ DownloadProgress?.Invoke(this, new DownloadProgressEventArgs
+ {
+ BytesDone = status.CompletedLength,
+ BytesTotal = status.TotalLength,
+ Speed = status.DownloadSpeed
+ });
+ }
+ catch
+ {
+ await Cancel();
+ DownloadComplete?.Invoke(this, new DownloadCompleteEventArgs());
+ }
+ }
+ }
+}
diff --git a/server/RdtClient.Service/Services/Downloaders/IDownloader.cs b/server/RdtClient.Service/Services/Downloaders/IDownloader.cs
new file mode 100644
index 0000000..2ca0099
--- /dev/null
+++ b/server/RdtClient.Service/Services/Downloaders/IDownloader.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Threading.Tasks;
+
+namespace RdtClient.Service.Services.Downloaders
+{
+ public class DownloadCompleteEventArgs
+ {
+ public String Error { get; set; }
+ }
+
+ public class DownloadProgressEventArgs
+ {
+ public Int64 Speed { get; set; }
+ public Int64 BytesDone { get; set; }
+ public Int64 BytesTotal { get; set; }
+ }
+
+ public interface IDownloader
+ {
+ event EventHandler DownloadComplete;
+ event EventHandler DownloadProgress;
+ Task Download();
+ Task Cancel();
+ }
+}
diff --git a/server/RdtClient.Service/Services/Downloaders/MultiDownloader.cs b/server/RdtClient.Service/Services/Downloaders/MultiDownloader.cs
new file mode 100644
index 0000000..5f53502
--- /dev/null
+++ b/server/RdtClient.Service/Services/Downloaders/MultiDownloader.cs
@@ -0,0 +1,128 @@
+using System;
+using System.IO;
+using System.Net;
+using System.Threading.Tasks;
+using Downloader;
+using RdtClient.Data.Models.Internal;
+
+namespace RdtClient.Service.Services.Downloaders
+{
+ public class MultiDownloader : IDownloader
+ {
+ public event EventHandler DownloadComplete;
+ public event EventHandler DownloadProgress;
+
+ private readonly DownloadService _downloadService;
+ private readonly String _filePath;
+ private readonly String _uri;
+
+ public MultiDownloader(String uri, String filePath, DbSettings settings)
+ {
+ _uri = uri;
+ _filePath = filePath;
+
+ var settingTempPath = settings.TempPath;
+
+ if (String.IsNullOrWhiteSpace(settingTempPath))
+ {
+ settingTempPath = Path.GetTempPath();
+ }
+
+ var settingDownloadChunkCount = settings.DownloadChunkCount;
+
+ if (settingDownloadChunkCount <= 0)
+ {
+ settingDownloadChunkCount = 1;
+ }
+
+ var settingDownloadMaxSpeed = settings.DownloadMaxSpeed;
+
+ if (settingDownloadMaxSpeed <= 0)
+ {
+ settingDownloadMaxSpeed = 0;
+ }
+
+ settingDownloadMaxSpeed = settingDownloadMaxSpeed * 1024 * 1024;
+
+ var settingProxyServer = settings.ProxyServer;
+
+ var downloadOpt = new DownloadConfiguration
+ {
+ MaxTryAgainOnFailover = Int32.MaxValue,
+ ParallelDownload = settingDownloadChunkCount > 1,
+ ChunkCount = settingDownloadChunkCount,
+ Timeout = 1000,
+ OnTheFlyDownload = false,
+ BufferBlockSize = 1024 * 8,
+ MaximumBytesPerSecond = settingDownloadMaxSpeed,
+ TempDirectory = settingTempPath,
+ RequestConfiguration =
+ {
+ Accept = "*/*",
+ UserAgent = $"rdt-client",
+ ProtocolVersion = HttpVersion.Version11,
+ AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
+ KeepAlive = true,
+ UseDefaultCredentials = false
+ }
+ };
+
+ if (!String.IsNullOrWhiteSpace(settingProxyServer))
+ {
+ downloadOpt.RequestConfiguration.Proxy = new WebProxy(new Uri(settingProxyServer), false);
+ }
+
+ _downloadService = new DownloadService(downloadOpt);
+
+ _downloadService.DownloadProgressChanged += (_, args) =>
+ {
+ if (DownloadProgress == null)
+ {
+ return;
+ }
+
+ DownloadProgress?.Invoke(this,
+ new DownloadProgressEventArgs
+ {
+ Speed = (Int64)args.BytesPerSecondSpeed,
+ BytesDone = args.ReceivedBytesSize,
+ BytesTotal = args.TotalBytesToReceive
+ });
+ };
+
+ _downloadService.DownloadFileCompleted += (_, args) =>
+ {
+ String error = null;
+
+ if (args.Cancelled)
+ {
+ error = $"The download was cancelled";
+ }
+ else if (args.Error != null)
+ {
+ error = args.Error.Message;
+ }
+
+ DownloadComplete?.Invoke(this,
+ new DownloadCompleteEventArgs
+ {
+ Error = error
+ });
+ };
+ }
+
+ public async Task Download()
+ {
+ await _downloadService.DownloadFileTaskAsync(_uri, _filePath);
+
+ return null;
+ }
+
+ public Task Cancel()
+ {
+ _downloadService.CancelAsync();
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/server/RdtClient.Service/Services/SimpleDownloader.cs b/server/RdtClient.Service/Services/Downloaders/SimpleDownloader.cs
similarity index 65%
rename from server/RdtClient.Service/Services/SimpleDownloader.cs
rename to server/RdtClient.Service/Services/Downloaders/SimpleDownloader.cs
index 50f783a..7943d22 100644
--- a/server/RdtClient.Service/Services/SimpleDownloader.cs
+++ b/server/RdtClient.Service/Services/Downloaders/SimpleDownloader.cs
@@ -1,27 +1,51 @@
using System;
-using System.ComponentModel;
using System.IO;
using System.Net;
using System.Threading.Tasks;
-using DownloadProgressChangedEventArgs = Downloader.DownloadProgressChangedEventArgs;
-namespace RdtClient.Service.Services
+namespace RdtClient.Service.Services.Downloaders
{
- public class SimpleDownloader
+ public class SimpleDownloader : IDownloader
{
- public Int64 Speed { get; private set; }
- public Int64 BytesTotal { get; private set; }
- public Int64 BytesDone { get; private set; }
+ public event EventHandler DownloadComplete;
+ public event EventHandler DownloadProgress;
- public event EventHandler DownloadFileCompleted;
- public event EventHandler DownloadProgressChanged;
+ private readonly String _uri;
+ private readonly String _filePath;
- private Boolean _cancelled = false;
+ private Int64 Speed { get; set; }
+ private Int64 BytesTotal { get; set; }
+ private Int64 BytesDone { get; set; }
+
+ private Boolean _cancelled;
private Int64 _bytesLastUpdate;
private DateTime _nextUpdate;
- public async Task Download(Uri uri, String filePath)
+ public SimpleDownloader(String uri, String filePath)
+ {
+ _uri = uri;
+ _filePath = filePath;
+ }
+
+ public Task Download()
+ {
+ Task.Run(async () =>
+ {
+ await StartDownloadTask();
+ });
+
+ return null;
+ }
+
+ public Task Cancel()
+ {
+ _cancelled = true;
+
+ return Task.CompletedTask;
+ }
+
+ private async Task StartDownloadTask()
{
try
{
@@ -29,7 +53,7 @@ namespace RdtClient.Service.Services
_nextUpdate = DateTime.UtcNow.AddSeconds(1);
// Determine the file size
- var webRequest = WebRequest.Create(uri);
+ var webRequest = WebRequest.Create(_uri);
webRequest.Method = "HEAD";
webRequest.Timeout = 5000;
Int64 responseLength;
@@ -45,7 +69,7 @@ namespace RdtClient.Service.Services
{
try
{
- var request = WebRequest.Create(uri);
+ var request = WebRequest.Create(_uri);
using var response = await request.GetResponseAsync();
await using var stream = response.GetResponseStream();
@@ -55,7 +79,7 @@ namespace RdtClient.Service.Services
throw new IOException("No stream");
}
- await using var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Write);
+ await using var fileStream = new FileStream(_filePath, FileMode.Create, FileAccess.Write, FileShare.Write);
var buffer = new Byte[64 * 1024];
while (fileStream.Length < response.ContentLength && !_cancelled)
@@ -78,13 +102,11 @@ namespace RdtClient.Service.Services
timeout = DateTimeOffset.UtcNow.AddHours(1);
- DownloadProgressChanged?.Invoke(this, new DownloadProgressChangedEventArgs(null)
+ DownloadProgress?.Invoke(this, new DownloadProgressEventArgs
{
- BytesPerSecondSpeed = Speed,
- ProgressedByteSize = _bytesLastUpdate,
- TotalBytesToReceive = BytesTotal,
- AverageBytesPerSecondSpeed = Speed,
- ReceivedBytesSize = BytesDone,
+ Speed = Speed,
+ BytesDone = BytesDone,
+ BytesTotal = BytesTotal
});
}
}
@@ -111,17 +133,15 @@ namespace RdtClient.Service.Services
throw new Exception($"Download timed out");
}
- DownloadFileCompleted?.Invoke(this, new AsyncCompletedEventArgs(null, false, null));
+ DownloadComplete?.Invoke(this, new DownloadCompleteEventArgs());
}
catch (Exception ex)
{
- DownloadFileCompleted?.Invoke(this, new AsyncCompletedEventArgs(ex, false, null));
+ DownloadComplete?.Invoke(this, new DownloadCompleteEventArgs
+ {
+ Error = ex.Message
+ });
}
}
-
- public void Cancel()
- {
- _cancelled = true;
- }
}
}
diff --git a/server/RdtClient.Service/Services/Downloads.cs b/server/RdtClient.Service/Services/Downloads.cs
index e2c3aae..aa50b49 100644
--- a/server/RdtClient.Service/Services/Downloads.cs
+++ b/server/RdtClient.Service/Services/Downloads.cs
@@ -74,6 +74,11 @@ namespace RdtClient.Service.Services
await _downloadData.UpdateRetryCount(downloadId, retryCount);
}
+ public async Task UpdateRemoteId(Guid downloadId, String remoteId)
+ {
+ await _downloadData.UpdateRemoteId(downloadId, remoteId);
+ }
+
public async Task DeleteForTorrent(Guid torrentId)
{
await _downloadData.DeleteForTorrent(torrentId);
diff --git a/server/RdtClient.Service/Services/Settings.cs b/server/RdtClient.Service/Services/Settings.cs
index fc33774..df44408 100644
--- a/server/RdtClient.Service/Services/Settings.cs
+++ b/server/RdtClient.Service/Services/Settings.cs
@@ -78,7 +78,7 @@ namespace RdtClient.Service.Services
var downloadClient = new DownloadClient(download, download.Torrent, downloadPath);
- await downloadClient.Start(Get);
+ downloadClient.Start(Get);
while (!downloadClient.Finished)
{
diff --git a/server/RdtClient.Service/Services/TorrentRunner.cs b/server/RdtClient.Service/Services/TorrentRunner.cs
index 45d0640..bbfc106 100644
--- a/server/RdtClient.Service/Services/TorrentRunner.cs
+++ b/server/RdtClient.Service/Services/TorrentRunner.cs
@@ -249,7 +249,14 @@ namespace RdtClient.Service.Services
{
Log.Debug($"Added download {download.DownloadId} to active downloads");
- await downloadClient.Start(Settings.Get);
+ var remoteId = await downloadClient.Start(Settings.Get);
+
+ if (!String.IsNullOrWhiteSpace(remoteId) && download.RemoteId != remoteId)
+ {
+ Log.Debug($"Received ID {remoteId} for download {download.DownloadId}");
+
+ await _downloads.UpdateRemoteId(download.DownloadId, remoteId);
+ }
Log.Debug($"Download {download.DownloadId} started");
}
@@ -335,7 +342,7 @@ namespace RdtClient.Service.Services
{
Log.Debug($"Added unpack {download.DownloadId} to active unpacks");
- await unpackClient.Start();
+ unpackClient.Start();
Log.Debug($"Unpack {download.DownloadId} started");
}
diff --git a/server/RdtClient.Service/Services/Torrents.cs b/server/RdtClient.Service/Services/Torrents.cs
index 808ec20..bd2045b 100644
--- a/server/RdtClient.Service/Services/Torrents.cs
+++ b/server/RdtClient.Service/Services/Torrents.cs
@@ -514,6 +514,18 @@ namespace RdtClient.Service.Services
return torrent;
}
+ private static String DownloadPath(Torrent torrent)
+ {
+ var settingDownloadPath = Settings.Get.DownloadPath;
+
+ if (!String.IsNullOrWhiteSpace(torrent.Category))
+ {
+ settingDownloadPath = Path.Combine(settingDownloadPath, torrent.Category);
+ }
+
+ return settingDownloadPath;
+ }
+
private async Task Add(String rdTorrentId,
String infoHash,
String category,
@@ -553,18 +565,6 @@ namespace RdtClient.Service.Services
}
}
- private String DownloadPath(Torrent torrent)
- {
- var settingDownloadPath = Settings.Get.DownloadPath;
-
- if (!String.IsNullOrWhiteSpace(torrent.Category))
- {
- settingDownloadPath = Path.Combine(settingDownloadPath, torrent.Category);
- }
-
- return settingDownloadPath;
- }
-
private async Task Update(Torrent torrent)
{
var originalTorrent = JsonConvert.SerializeObject(torrent,
diff --git a/server/RdtClient.Service/Services/UnpackClient.cs b/server/RdtClient.Service/Services/UnpackClient.cs
index a79f12d..9dda73d 100644
--- a/server/RdtClient.Service/Services/UnpackClient.cs
+++ b/server/RdtClient.Service/Services/UnpackClient.cs
@@ -36,7 +36,7 @@ namespace RdtClient.Service.Services
_torrent = download.Torrent;
}
- public async Task Start()
+ public void Start()
{
BytesDone = 0;
BytesTotal = 0;
@@ -50,9 +50,7 @@ namespace RdtClient.Service.Services
throw new Exception("Invalid download path");
}
-#pragma warning disable 4014
Task.Run(async delegate
-#pragma warning restore 4014
{
if (!_cancelled)
{
diff --git a/server/RdtClient.Web/RdtClient.Web.csproj b/server/RdtClient.Web/RdtClient.Web.csproj
index 23ce87d..2a9bf6c 100644
--- a/server/RdtClient.Web/RdtClient.Web.csproj
+++ b/server/RdtClient.Web/RdtClient.Web.csproj
@@ -26,7 +26,6 @@
-