Add first Aria2c support.
This commit is contained in:
parent
b8b50ca55a
commit
c728e17bb3
23 changed files with 940 additions and 195 deletions
|
|
@ -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.
|
- 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.
|
- 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
|
### Troubleshooting
|
||||||
|
|
||||||
- If you forgot your logins simply delete the `rdtclient.db` and restart the service.
|
- If you forgot your logins simply delete the `rdtclient.db` and restart the service.
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,7 @@
|
||||||
<select [(ngModel)]="settingDownloadClient">
|
<select [(ngModel)]="settingDownloadClient">
|
||||||
<option value="Simple">Simple Downloader</option>
|
<option value="Simple">Simple Downloader</option>
|
||||||
<option value="MultiPart">Multi Part Downloader</option>
|
<option value="MultiPart">Multi Part Downloader</option>
|
||||||
|
<option value="Aria2c">Aria2c</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<p class="help">
|
<p class="help">
|
||||||
|
|
@ -112,6 +113,7 @@
|
||||||
parallel downloading will be done.
|
parallel downloading will be done.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field" *ngIf="settingDownloadClient === 'MultiPart'">
|
<div class="field" *ngIf="settingDownloadClient === 'MultiPart'">
|
||||||
<label class="label">Download speed (in MB/s)</label>
|
<label class="label">Download speed (in MB/s)</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
|
|
@ -119,6 +121,7 @@
|
||||||
</div>
|
</div>
|
||||||
<p class="help">Maximum download speed in Megabytes per second. When set to 0 unlimited speed is used.</p>
|
<p class="help">Maximum download speed in Megabytes per second. When set to 0 unlimited speed is used.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field" *ngIf="settingDownloadClient === 'MultiPart'">
|
<div class="field" *ngIf="settingDownloadClient === 'MultiPart'">
|
||||||
<label class="label">Proxy Server</label>
|
<label class="label">Proxy Server</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
|
|
@ -126,6 +129,27 @@
|
||||||
</div>
|
</div>
|
||||||
<p class="help">Address of a proxy server.</p>
|
<p class="help">Address of a proxy server.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="field" *ngIf="settingDownloadClient === 'Aria2c'">
|
||||||
|
<label class="label">Aria2c URL</label>
|
||||||
|
<div class="control">
|
||||||
|
<input class="input" type="text" maxlength="1000" [(ngModel)]="aria2cUrl" />
|
||||||
|
</div>
|
||||||
|
<p class="help">
|
||||||
|
This is the URL to your Aria2c instance. It must end in /jsonrpc. A common URL is
|
||||||
|
http://192.168.10.2:6800/jsonrpc.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field" *ngIf="settingDownloadClient === 'Aria2c'">
|
||||||
|
<label class="label">Aria2c Secret</label>
|
||||||
|
<div class="control">
|
||||||
|
<input class="input" type="text" maxlength="1000" [(ngModel)]="aria2cSecret" />
|
||||||
|
</div>
|
||||||
|
<p class="help">
|
||||||
|
The secret of your Aria2c instance. Optional.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="activeTab === 2">
|
<div *ngIf="activeTab === 2">
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,9 @@ export class SettingsComponent implements OnInit {
|
||||||
public settingOnlyDownloadAvailableFiles: boolean;
|
public settingOnlyDownloadAvailableFiles: boolean;
|
||||||
public settingProxyServer: string;
|
public settingProxyServer: string;
|
||||||
|
|
||||||
|
public aria2cUrl: string;
|
||||||
|
public aria2cSecret: string;
|
||||||
|
|
||||||
constructor(private settingsService: SettingsService) {}
|
constructor(private settingsService: SettingsService) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
|
@ -61,6 +64,8 @@ export class SettingsComponent implements OnInit {
|
||||||
this.settingMinFileSize = parseInt(this.getSetting(results, 'MinFileSize'), 10);
|
this.settingMinFileSize = parseInt(this.getSetting(results, 'MinFileSize'), 10);
|
||||||
this.settingOnlyDownloadAvailableFiles = this.getSetting(results, 'OnlyDownloadAvailableFiles') === '1';
|
this.settingOnlyDownloadAvailableFiles = this.getSetting(results, 'OnlyDownloadAvailableFiles') === '1';
|
||||||
this.settingProxyServer = this.getSetting(results, 'ProxyServer');
|
this.settingProxyServer = this.getSetting(results, 'ProxyServer');
|
||||||
|
this.aria2cUrl = this.getSetting(results, 'Aria2cUrl');
|
||||||
|
this.aria2cSecret = this.getSetting(results, 'Aria2cSecret');
|
||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
this.error = err.error;
|
this.error = err.error;
|
||||||
|
|
@ -125,6 +130,14 @@ export class SettingsComponent implements OnInit {
|
||||||
settingId: 'ProxyServer',
|
settingId: 'ProxyServer',
|
||||||
value: this.settingProxyServer,
|
value: this.settingProxyServer,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
settingId: 'Aria2cUrl',
|
||||||
|
value: this.aria2cUrl,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
settingId: 'Aria2cSecret',
|
||||||
|
value: this.aria2cSecret,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
this.settingsService.update(settings).subscribe(
|
this.settingsService.update(settings).subscribe(
|
||||||
|
|
|
||||||
|
|
@ -135,6 +135,18 @@ namespace RdtClient.Data.Data
|
||||||
SettingId = "Categories",
|
SettingId = "Categories",
|
||||||
Type = "String",
|
Type = "String",
|
||||||
Value = ""
|
Value = ""
|
||||||
|
},
|
||||||
|
new Setting
|
||||||
|
{
|
||||||
|
SettingId = "Aria2cUrl",
|
||||||
|
Type = "String",
|
||||||
|
Value = "http://127.0.0.1:6800/jsonrpc"
|
||||||
|
},
|
||||||
|
new Setting
|
||||||
|
{
|
||||||
|
SettingId = "Aria2cSecret",
|
||||||
|
Type = "String",
|
||||||
|
Value = ""
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -164,6 +164,16 @@ namespace RdtClient.Data.Data
|
||||||
await TorrentData.VoidCache();
|
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)
|
public async Task DeleteForTorrent(Guid torrentId)
|
||||||
{
|
{
|
||||||
var downloads = await _dataContext.Downloads
|
var downloads = await _dataContext.Downloads
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,9 @@ namespace RdtClient.Data.Data
|
||||||
DownloadMaxSpeed = GetInt32("DownloadMaxSpeed"),
|
DownloadMaxSpeed = GetInt32("DownloadMaxSpeed"),
|
||||||
ProxyServer = GetString("ProxyServer"),
|
ProxyServer = GetString("ProxyServer"),
|
||||||
LogLevel = GetString("LogLevel"),
|
LogLevel = GetString("LogLevel"),
|
||||||
Categories = GetString("Categories")
|
Categories = GetString("Categories"),
|
||||||
|
Aria2cUrl = GetString("Aria2cUrl"),
|
||||||
|
Aria2cSecret = GetString("Aria2cSecret"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
436
server/RdtClient.Data/Migrations/20211009221133_Downloads_Add_RemoteId.Designer.cs
generated
Normal file
436
server/RdtClient.Data/Migrations/20211009221133_Downloads_Add_RemoteId.Designer.cs
generated
Normal file
|
|
@ -0,0 +1,436 @@
|
||||||
|
// <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("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<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<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.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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<string>(
|
||||||
|
name: "RemoteId",
|
||||||
|
table: "Downloads",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "RemoteId",
|
||||||
|
table: "Downloads");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,7 +14,7 @@ namespace RdtClient.Data.Migrations
|
||||||
{
|
{
|
||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasAnnotation("ProductVersion", "5.0.8");
|
.HasAnnotation("ProductVersion", "5.0.10");
|
||||||
|
|
||||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||||
{
|
{
|
||||||
|
|
@ -238,6 +238,9 @@ namespace RdtClient.Data.Migrations
|
||||||
b.Property<string>("Path")
|
b.Property<string>("Path")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("RemoteId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<int>("RetryCount")
|
b.Property<int>("RetryCount")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,8 @@ namespace RdtClient.Data.Models.Data
|
||||||
|
|
||||||
public String Error { get; set; }
|
public String Error { get; set; }
|
||||||
|
|
||||||
|
public String RemoteId { get; set; }
|
||||||
|
|
||||||
[ForeignKey("TorrentId")]
|
[ForeignKey("TorrentId")]
|
||||||
public Torrent Torrent { get; set; }
|
public Torrent Torrent { get; set; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,5 +18,7 @@ namespace RdtClient.Data.Models.Internal
|
||||||
public String ProxyServer { get; set; }
|
public String ProxyServer { get; set; }
|
||||||
public String LogLevel { get; set; }
|
public String LogLevel { get; set; }
|
||||||
public String Categories { get; set; }
|
public String Categories { get; set; }
|
||||||
|
public String Aria2cUrl { get; set; }
|
||||||
|
public String Aria2cSecret { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Aria2.NET" Version="1.0.1" />
|
||||||
<PackageReference Include="Downloader" Version="2.2.9" />
|
<PackageReference Include="Downloader" Version="2.2.9" />
|
||||||
<PackageReference Include="MonoTorrent" Version="2.0.0" />
|
<PackageReference Include="MonoTorrent" Version="2.0.0" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Downloader;
|
|
||||||
using RdtClient.Data.Models.Data;
|
using RdtClient.Data.Models.Data;
|
||||||
using RdtClient.Data.Models.Internal;
|
using RdtClient.Data.Models.Internal;
|
||||||
using RdtClient.Service.Helpers;
|
using RdtClient.Service.Helpers;
|
||||||
|
using RdtClient.Service.Services.Downloaders;
|
||||||
|
|
||||||
namespace RdtClient.Service.Services
|
namespace RdtClient.Service.Services
|
||||||
{
|
{
|
||||||
|
|
@ -16,8 +15,7 @@ namespace RdtClient.Service.Services
|
||||||
private readonly Download _download;
|
private readonly Download _download;
|
||||||
private readonly Torrent _torrent;
|
private readonly Torrent _torrent;
|
||||||
|
|
||||||
private DownloadService _downloader;
|
private IDownloader _downloader;
|
||||||
private SimpleDownloader _simpleDownloader;
|
|
||||||
|
|
||||||
public DownloadClient(Download download, Torrent torrent, String destinationPath)
|
public DownloadClient(Download download, Torrent torrent, String destinationPath)
|
||||||
{
|
{
|
||||||
|
|
@ -34,7 +32,7 @@ namespace RdtClient.Service.Services
|
||||||
public Int64 BytesTotal { get; private set; }
|
public Int64 BytesTotal { get; private set; }
|
||||||
public Int64 BytesDone { get; private set; }
|
public Int64 BytesDone { get; private set; }
|
||||||
|
|
||||||
public async Task Start(DbSettings settings)
|
public async Task<String> Start(DbSettings settings)
|
||||||
{
|
{
|
||||||
BytesDone = 0;
|
BytesDone = 0;
|
||||||
BytesTotal = 0;
|
BytesTotal = 0;
|
||||||
|
|
@ -54,159 +52,41 @@ namespace RdtClient.Service.Services
|
||||||
File.Delete(filePath);
|
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
|
_downloader.DownloadComplete += (_, args) =>
|
||||||
Task.Run(async delegate
|
{
|
||||||
#pragma warning restore 4014
|
Finished = true;
|
||||||
{
|
Error = args.Error;
|
||||||
switch (settings.DownloadClient)
|
};
|
||||||
{
|
|
||||||
case "Simple":
|
_downloader.DownloadProgress += (_, args) =>
|
||||||
await DownloadSimple(uri, filePath);
|
{
|
||||||
break;
|
Speed = args.Speed;
|
||||||
case "MultiPart":
|
BytesDone = args.BytesDone;
|
||||||
await DownloadMultiPart(filePath, settings);
|
BytesTotal = args.BytesTotal;
|
||||||
break;
|
};
|
||||||
default:
|
|
||||||
throw new Exception($"Unknown download client {settings.DownloadClient}");
|
return await _downloader.Download();
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Error = $"An unexpected error occurred preparing download {_download.Link} for torrent {_torrent.RdName}: {ex.Message}";
|
Error = $"An unexpected error occurred preparing download {_download.Link} for torrent {_torrent.RdName}: {ex.Message}";
|
||||||
Finished = true;
|
Finished = true;
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Cancel()
|
public void Cancel()
|
||||||
{
|
{
|
||||||
_downloader?.CancelAsync();
|
_downloader?.Cancel();
|
||||||
_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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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<DownloadCompleteEventArgs> DownloadComplete;
|
||||||
|
public event EventHandler<DownloadProgressEventArgs> 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<String> 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<String>
|
||||||
|
{
|
||||||
|
_uri
|
||||||
|
},
|
||||||
|
new Dictionary<String, Object>
|
||||||
|
{
|
||||||
|
{
|
||||||
|
"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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
server/RdtClient.Service/Services/Downloaders/IDownloader.cs
Normal file
25
server/RdtClient.Service/Services/Downloaders/IDownloader.cs
Normal file
|
|
@ -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<DownloadCompleteEventArgs> DownloadComplete;
|
||||||
|
event EventHandler<DownloadProgressEventArgs> DownloadProgress;
|
||||||
|
Task<String> Download();
|
||||||
|
Task Cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
128
server/RdtClient.Service/Services/Downloaders/MultiDownloader.cs
Normal file
128
server/RdtClient.Service/Services/Downloaders/MultiDownloader.cs
Normal file
|
|
@ -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<DownloadCompleteEventArgs> DownloadComplete;
|
||||||
|
public event EventHandler<DownloadProgressEventArgs> 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<String> Download()
|
||||||
|
{
|
||||||
|
await _downloadService.DownloadFileTaskAsync(_uri, _filePath);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Cancel()
|
||||||
|
{
|
||||||
|
_downloadService.CancelAsync();
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,27 +1,51 @@
|
||||||
using System;
|
using System;
|
||||||
using System.ComponentModel;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Threading.Tasks;
|
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 event EventHandler<DownloadCompleteEventArgs> DownloadComplete;
|
||||||
public Int64 BytesTotal { get; private set; }
|
public event EventHandler<DownloadProgressEventArgs> DownloadProgress;
|
||||||
public Int64 BytesDone { get; private set; }
|
|
||||||
|
|
||||||
public event EventHandler<AsyncCompletedEventArgs> DownloadFileCompleted;
|
private readonly String _uri;
|
||||||
public event EventHandler<DownloadProgressChangedEventArgs> DownloadProgressChanged;
|
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 Int64 _bytesLastUpdate;
|
||||||
private DateTime _nextUpdate;
|
private DateTime _nextUpdate;
|
||||||
|
|
||||||
public async Task Download(Uri uri, String filePath)
|
public SimpleDownloader(String uri, String filePath)
|
||||||
|
{
|
||||||
|
_uri = uri;
|
||||||
|
_filePath = filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<String> Download()
|
||||||
|
{
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await StartDownloadTask();
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Cancel()
|
||||||
|
{
|
||||||
|
_cancelled = true;
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task StartDownloadTask()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -29,7 +53,7 @@ namespace RdtClient.Service.Services
|
||||||
_nextUpdate = DateTime.UtcNow.AddSeconds(1);
|
_nextUpdate = DateTime.UtcNow.AddSeconds(1);
|
||||||
|
|
||||||
// Determine the file size
|
// Determine the file size
|
||||||
var webRequest = WebRequest.Create(uri);
|
var webRequest = WebRequest.Create(_uri);
|
||||||
webRequest.Method = "HEAD";
|
webRequest.Method = "HEAD";
|
||||||
webRequest.Timeout = 5000;
|
webRequest.Timeout = 5000;
|
||||||
Int64 responseLength;
|
Int64 responseLength;
|
||||||
|
|
@ -45,7 +69,7 @@ namespace RdtClient.Service.Services
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var request = WebRequest.Create(uri);
|
var request = WebRequest.Create(_uri);
|
||||||
using var response = await request.GetResponseAsync();
|
using var response = await request.GetResponseAsync();
|
||||||
|
|
||||||
await using var stream = response.GetResponseStream();
|
await using var stream = response.GetResponseStream();
|
||||||
|
|
@ -55,7 +79,7 @@ namespace RdtClient.Service.Services
|
||||||
throw new IOException("No stream");
|
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];
|
var buffer = new Byte[64 * 1024];
|
||||||
|
|
||||||
while (fileStream.Length < response.ContentLength && !_cancelled)
|
while (fileStream.Length < response.ContentLength && !_cancelled)
|
||||||
|
|
@ -78,13 +102,11 @@ namespace RdtClient.Service.Services
|
||||||
|
|
||||||
timeout = DateTimeOffset.UtcNow.AddHours(1);
|
timeout = DateTimeOffset.UtcNow.AddHours(1);
|
||||||
|
|
||||||
DownloadProgressChanged?.Invoke(this, new DownloadProgressChangedEventArgs(null)
|
DownloadProgress?.Invoke(this, new DownloadProgressEventArgs
|
||||||
{
|
{
|
||||||
BytesPerSecondSpeed = Speed,
|
Speed = Speed,
|
||||||
ProgressedByteSize = _bytesLastUpdate,
|
BytesDone = BytesDone,
|
||||||
TotalBytesToReceive = BytesTotal,
|
BytesTotal = BytesTotal
|
||||||
AverageBytesPerSecondSpeed = Speed,
|
|
||||||
ReceivedBytesSize = BytesDone,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -111,17 +133,15 @@ namespace RdtClient.Service.Services
|
||||||
throw new Exception($"Download timed out");
|
throw new Exception($"Download timed out");
|
||||||
}
|
}
|
||||||
|
|
||||||
DownloadFileCompleted?.Invoke(this, new AsyncCompletedEventArgs(null, false, null));
|
DownloadComplete?.Invoke(this, new DownloadCompleteEventArgs());
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
DownloadFileCompleted?.Invoke(this, new AsyncCompletedEventArgs(ex, false, null));
|
DownloadComplete?.Invoke(this, new DownloadCompleteEventArgs
|
||||||
|
{
|
||||||
|
Error = ex.Message
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Cancel()
|
|
||||||
{
|
|
||||||
_cancelled = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -74,6 +74,11 @@ namespace RdtClient.Service.Services
|
||||||
await _downloadData.UpdateRetryCount(downloadId, retryCount);
|
await _downloadData.UpdateRetryCount(downloadId, retryCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task UpdateRemoteId(Guid downloadId, String remoteId)
|
||||||
|
{
|
||||||
|
await _downloadData.UpdateRemoteId(downloadId, remoteId);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task DeleteForTorrent(Guid torrentId)
|
public async Task DeleteForTorrent(Guid torrentId)
|
||||||
{
|
{
|
||||||
await _downloadData.DeleteForTorrent(torrentId);
|
await _downloadData.DeleteForTorrent(torrentId);
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ namespace RdtClient.Service.Services
|
||||||
|
|
||||||
var downloadClient = new DownloadClient(download, download.Torrent, downloadPath);
|
var downloadClient = new DownloadClient(download, download.Torrent, downloadPath);
|
||||||
|
|
||||||
await downloadClient.Start(Get);
|
downloadClient.Start(Get);
|
||||||
|
|
||||||
while (!downloadClient.Finished)
|
while (!downloadClient.Finished)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -249,7 +249,14 @@ namespace RdtClient.Service.Services
|
||||||
{
|
{
|
||||||
Log.Debug($"Added download {download.DownloadId} to active downloads");
|
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");
|
Log.Debug($"Download {download.DownloadId} started");
|
||||||
}
|
}
|
||||||
|
|
@ -335,7 +342,7 @@ namespace RdtClient.Service.Services
|
||||||
{
|
{
|
||||||
Log.Debug($"Added unpack {download.DownloadId} to active unpacks");
|
Log.Debug($"Added unpack {download.DownloadId} to active unpacks");
|
||||||
|
|
||||||
await unpackClient.Start();
|
unpackClient.Start();
|
||||||
|
|
||||||
Log.Debug($"Unpack {download.DownloadId} started");
|
Log.Debug($"Unpack {download.DownloadId} started");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -514,6 +514,18 @@ namespace RdtClient.Service.Services
|
||||||
return torrent;
|
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,
|
private async Task Add(String rdTorrentId,
|
||||||
String infoHash,
|
String infoHash,
|
||||||
String category,
|
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)
|
private async Task Update(Torrent torrent)
|
||||||
{
|
{
|
||||||
var originalTorrent = JsonConvert.SerializeObject(torrent,
|
var originalTorrent = JsonConvert.SerializeObject(torrent,
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ namespace RdtClient.Service.Services
|
||||||
_torrent = download.Torrent;
|
_torrent = download.Torrent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
BytesDone = 0;
|
BytesDone = 0;
|
||||||
BytesTotal = 0;
|
BytesTotal = 0;
|
||||||
|
|
@ -50,9 +50,7 @@ namespace RdtClient.Service.Services
|
||||||
throw new Exception("Invalid download path");
|
throw new Exception("Invalid download path");
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning disable 4014
|
|
||||||
Task.Run(async delegate
|
Task.Run(async delegate
|
||||||
#pragma warning restore 4014
|
|
||||||
{
|
{
|
||||||
if (!_cancelled)
|
if (!_cancelled)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Aria2.NET" Version="1.0.0" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.10" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.10" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="5.0.10" />
|
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="5.0.10" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.10" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.10" />
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue