Upgrade packages
Moved C# code to file scoped namespaces Merged Startup.cs and Program.cs
This commit is contained in:
parent
2cff8ec0a6
commit
cd6002b5d6
69 changed files with 7484 additions and 7591 deletions
1650
client/package-lock.json
generated
1650
client/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -10,33 +10,33 @@
|
|||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "~13.2.6",
|
||||
"@angular/cdk": "^13.2.6",
|
||||
"@angular/common": "~13.2.6",
|
||||
"@angular/compiler": "~13.2.6",
|
||||
"@angular/core": "~13.2.6",
|
||||
"@angular/animations": "~13.3.5",
|
||||
"@angular/cdk": "^13.3.5",
|
||||
"@angular/common": "~13.3.5",
|
||||
"@angular/compiler": "~13.3.5",
|
||||
"@angular/core": "~13.3.5",
|
||||
"@angular/flex-layout": "^13.0.0-beta.38",
|
||||
"@angular/forms": "~13.2.6",
|
||||
"@angular/platform-browser": "~13.2.6",
|
||||
"@angular/platform-browser-dynamic": "~13.2.6",
|
||||
"@angular/router": "~13.2.6",
|
||||
"@fortawesome/fontawesome-free": "^6.0.0",
|
||||
"@microsoft/signalr": "^6.0.3",
|
||||
"@angular/forms": "~13.3.5",
|
||||
"@angular/platform-browser": "~13.3.5",
|
||||
"@angular/platform-browser-dynamic": "~13.3.5",
|
||||
"@angular/router": "~13.3.5",
|
||||
"@fortawesome/fontawesome-free": "^6.1.1",
|
||||
"@microsoft/signalr": "^6.0.4",
|
||||
"bulma": "^0.9.3",
|
||||
"curray": "^1.0.8",
|
||||
"file-saver": "^2.0.5",
|
||||
"ngx-filesize": "^2.0.16",
|
||||
"rxjs": "~7.5.5",
|
||||
"tslib": "^2.3.1",
|
||||
"tslib": "^2.4.0",
|
||||
"zone.js": "~0.11.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "~13.2.6",
|
||||
"@angular/cli": "~13.2.6",
|
||||
"@angular/compiler-cli": "~13.2.6",
|
||||
"@angular/language-service": "~13.2.6",
|
||||
"@angular-devkit/build-angular": "~13.3.4",
|
||||
"@angular/cli": "~13.3.4",
|
||||
"@angular/compiler-cli": "~13.3.5",
|
||||
"@angular/language-service": "~13.3.5",
|
||||
"@types/file-saver": "^2.0.5",
|
||||
"@types/node": "^17.0.21",
|
||||
"@types/node": "^17.0.30",
|
||||
"typescript": "~4.5.5"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,225 +1,221 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RdtClient.Data.Models.Data;
|
||||
|
||||
namespace RdtClient.Data.Data
|
||||
namespace RdtClient.Data.Data;
|
||||
|
||||
public class DataContext : IdentityDbContext
|
||||
{
|
||||
public class DataContext : IdentityDbContext
|
||||
public DataContext(DbContextOptions options) : base(options)
|
||||
{
|
||||
public DataContext(DbContextOptions options) : base(options)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public DbSet<Download> Downloads { get; set; }
|
||||
public DbSet<Setting> Settings { get; set; }
|
||||
public DbSet<Torrent> Torrents { get; set; }
|
||||
public DbSet<Download> Downloads { get; set; }
|
||||
public DbSet<Setting> Settings { get; set; }
|
||||
public DbSet<Torrent> Torrents { get; set; }
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder options)
|
||||
{
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder options)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
base.OnModelCreating(builder);
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
{
|
||||
base.OnModelCreating(builder);
|
||||
|
||||
var cascadeFKs = builder.Model.GetEntityTypes()
|
||||
.SelectMany(t => t.GetForeignKeys())
|
||||
.Where(fk => !fk.IsOwnership && fk.DeleteBehavior == DeleteBehavior.Cascade);
|
||||
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 async Task Seed()
|
||||
foreach (var fk in cascadeFKs)
|
||||
{
|
||||
var seedSettings = new List<Setting>
|
||||
fk.DeleteBehavior = DeleteBehavior.Restrict;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Seed()
|
||||
{
|
||||
var seedSettings = new List<Setting>
|
||||
{
|
||||
new Setting
|
||||
{
|
||||
new Setting
|
||||
{
|
||||
SettingId = "Provider",
|
||||
Type = "String",
|
||||
Value = "RealDebrid"
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "ProviderAutoImport",
|
||||
Type = "Int32",
|
||||
Value = "0"
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "ProviderAutoImportCategory",
|
||||
Type = "String",
|
||||
Value = ""
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "ProviderAutoDelete",
|
||||
Type = "Int32",
|
||||
Value = "0"
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "DeleteOnError",
|
||||
Type = "Int32",
|
||||
Value = "0"
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "TorrentLifetime",
|
||||
Type = "Int32",
|
||||
Value = "0"
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "RealDebridApiKey",
|
||||
Type = "String",
|
||||
Value = ""
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "DownloadPath",
|
||||
Type = "String",
|
||||
#if DEBUG
|
||||
Value = @"C:\Temp\rdtclient"
|
||||
#else
|
||||
Value = "/data/downloads"
|
||||
#endif
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "DownloadClient",
|
||||
Type = "String",
|
||||
Value = @"Simple"
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "TempPath",
|
||||
Type = "String",
|
||||
SettingId = "Provider",
|
||||
Type = "String",
|
||||
Value = "RealDebrid"
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "ProviderAutoImport",
|
||||
Type = "Int32",
|
||||
Value = "0"
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "ProviderAutoImportCategory",
|
||||
Type = "String",
|
||||
Value = ""
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "ProviderAutoDelete",
|
||||
Type = "Int32",
|
||||
Value = "0"
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "DeleteOnError",
|
||||
Type = "Int32",
|
||||
Value = "0"
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "TorrentLifetime",
|
||||
Type = "Int32",
|
||||
Value = "0"
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "RealDebridApiKey",
|
||||
Type = "String",
|
||||
Value = ""
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "DownloadPath",
|
||||
Type = "String",
|
||||
#if DEBUG
|
||||
Value = @"C:\Temp\rdtclient"
|
||||
Value = @"C:\Temp\rdtclient"
|
||||
#else
|
||||
Value = "/data/downloads"
|
||||
#endif
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "MappedPath",
|
||||
Type = "String",
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "DownloadClient",
|
||||
Type = "String",
|
||||
Value = @"Simple"
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "TempPath",
|
||||
Type = "String",
|
||||
#if DEBUG
|
||||
Value = @"C:\Temp\rdtclient"
|
||||
Value = @"C:\Temp\rdtclient"
|
||||
#else
|
||||
Value = "/data/downloads"
|
||||
#endif
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "MappedPath",
|
||||
Type = "String",
|
||||
#if DEBUG
|
||||
Value = @"C:\Temp\rdtclient"
|
||||
#else
|
||||
Value = @"C:\Downloads"
|
||||
#endif
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "DownloadLimit",
|
||||
Type = "Int32",
|
||||
Value = "2"
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "UnpackLimit",
|
||||
Type = "Int32",
|
||||
Value = "1"
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "MinFileSize",
|
||||
Type = "Int32",
|
||||
Value = "0"
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "OnlyDownloadAvailableFiles",
|
||||
Type = "Int32",
|
||||
Value = "1"
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "DownloadChunkCount",
|
||||
Type = "Int32",
|
||||
Value = "8"
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "DownloadMaxSpeed",
|
||||
Type = "Int32",
|
||||
Value = "0"
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "ProxyServer",
|
||||
Type = "String",
|
||||
Value = ""
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "LogLevel",
|
||||
Type = "String",
|
||||
Value = "Warning"
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
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 = ""
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "DownloadRetryAttempts",
|
||||
Type = "Int32",
|
||||
Value = "3"
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "TorrentRetryAttempts",
|
||||
Type = "Int32",
|
||||
Value = "1"
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "RunOnTorrentCompleteFileName",
|
||||
Type = "String",
|
||||
Value = ""
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "RunOnTorrentCompleteArguments",
|
||||
Type = "String",
|
||||
Value = ""
|
||||
}
|
||||
};
|
||||
|
||||
var dbSettings = await Settings.ToListAsync();
|
||||
foreach (var seedSetting in seedSettings)
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
var dbSetting = dbSettings.FirstOrDefault(m => m.SettingId == seedSetting.SettingId);
|
||||
SettingId = "DownloadLimit",
|
||||
Type = "Int32",
|
||||
Value = "2"
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "UnpackLimit",
|
||||
Type = "Int32",
|
||||
Value = "1"
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "MinFileSize",
|
||||
Type = "Int32",
|
||||
Value = "0"
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "OnlyDownloadAvailableFiles",
|
||||
Type = "Int32",
|
||||
Value = "1"
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "DownloadChunkCount",
|
||||
Type = "Int32",
|
||||
Value = "8"
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "DownloadMaxSpeed",
|
||||
Type = "Int32",
|
||||
Value = "0"
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "ProxyServer",
|
||||
Type = "String",
|
||||
Value = ""
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "LogLevel",
|
||||
Type = "String",
|
||||
Value = "Warning"
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
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 = ""
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "DownloadRetryAttempts",
|
||||
Type = "Int32",
|
||||
Value = "3"
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "TorrentRetryAttempts",
|
||||
Type = "Int32",
|
||||
Value = "1"
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "RunOnTorrentCompleteFileName",
|
||||
Type = "String",
|
||||
Value = ""
|
||||
},
|
||||
new Setting
|
||||
{
|
||||
SettingId = "RunOnTorrentCompleteArguments",
|
||||
Type = "String",
|
||||
Value = ""
|
||||
}
|
||||
};
|
||||
|
||||
if (dbSetting == null)
|
||||
{
|
||||
await Settings.AddAsync(seedSetting);
|
||||
await SaveChangesAsync();
|
||||
}
|
||||
var dbSettings = await Settings.ToListAsync();
|
||||
foreach (var seedSetting in seedSettings)
|
||||
{
|
||||
var dbSetting = dbSettings.FirstOrDefault(m => m.SettingId == seedSetting.SettingId);
|
||||
|
||||
if (dbSetting == null)
|
||||
{
|
||||
await Settings.AddAsync(seedSetting);
|
||||
await SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,267 +1,262 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Download = RdtClient.Data.Models.Data.Download;
|
||||
|
||||
namespace RdtClient.Data.Data
|
||||
namespace RdtClient.Data.Data;
|
||||
|
||||
public class DownloadData
|
||||
{
|
||||
public class DownloadData
|
||||
private readonly DataContext _dataContext;
|
||||
|
||||
public DownloadData(DataContext dataContext)
|
||||
{
|
||||
private readonly DataContext _dataContext;
|
||||
_dataContext = dataContext;
|
||||
}
|
||||
|
||||
public DownloadData(DataContext dataContext)
|
||||
public async Task<List<Download>> GetForTorrent(Guid torrentId)
|
||||
{
|
||||
return await _dataContext.Downloads
|
||||
.AsNoTracking()
|
||||
.Where(m => m.TorrentId == torrentId)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<Download> GetById(Guid downloadId)
|
||||
{
|
||||
return await _dataContext.Downloads
|
||||
.Include(m => m.Torrent)
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||
}
|
||||
|
||||
public async Task<Download> Get(Guid torrentId, String path)
|
||||
{
|
||||
return await _dataContext.Downloads
|
||||
.Include(m => m.Torrent)
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(m => m.TorrentId == torrentId && m.Path == path);
|
||||
}
|
||||
|
||||
public async Task<Download> Add(Guid torrentId, String path)
|
||||
{
|
||||
var download = new Download
|
||||
{
|
||||
_dataContext = dataContext;
|
||||
DownloadId = Guid.NewGuid(),
|
||||
TorrentId = torrentId,
|
||||
Path = path,
|
||||
Added = DateTimeOffset.UtcNow,
|
||||
DownloadQueued = DateTimeOffset.UtcNow,
|
||||
RetryCount = 0
|
||||
};
|
||||
|
||||
await _dataContext.Downloads.AddAsync(download);
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await TorrentData.VoidCache();
|
||||
|
||||
return download;
|
||||
}
|
||||
|
||||
public async Task UpdateUnrestrictedLink(Guid downloadId, String unrestrictedLink)
|
||||
{
|
||||
var dbDownload = await _dataContext.Downloads
|
||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||
|
||||
if (dbDownload == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
public async Task<List<Download>> GetForTorrent(Guid torrentId)
|
||||
dbDownload.Link = unrestrictedLink;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await TorrentData.VoidCache();
|
||||
}
|
||||
|
||||
public async Task UpdateDownloadStarted(Guid downloadId, DateTimeOffset? dateTime)
|
||||
{
|
||||
var dbDownload = await _dataContext.Downloads
|
||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||
|
||||
if (dbDownload == null)
|
||||
{
|
||||
return await _dataContext.Downloads
|
||||
.AsNoTracking()
|
||||
.Where(m => m.TorrentId == torrentId)
|
||||
.ToListAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
public async Task<Download> GetById(Guid downloadId)
|
||||
dbDownload.DownloadStarted = dateTime;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await TorrentData.VoidCache();
|
||||
}
|
||||
|
||||
public async Task UpdateDownloadFinished(Guid downloadId, DateTimeOffset? dateTime)
|
||||
{
|
||||
var dbDownload = await _dataContext.Downloads
|
||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||
|
||||
if (dbDownload == null)
|
||||
{
|
||||
return await _dataContext.Downloads
|
||||
.Include(m => m.Torrent)
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||
return;
|
||||
}
|
||||
|
||||
public async Task<Download> Get(Guid torrentId, String path)
|
||||
{
|
||||
return await _dataContext.Downloads
|
||||
.Include(m => m.Torrent)
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(m => m.TorrentId == torrentId && m.Path == path);
|
||||
}
|
||||
|
||||
public async Task<Download> Add(Guid torrentId, String path)
|
||||
{
|
||||
var download = new Download
|
||||
{
|
||||
DownloadId = Guid.NewGuid(),
|
||||
TorrentId = torrentId,
|
||||
Path = path,
|
||||
Added = DateTimeOffset.UtcNow,
|
||||
DownloadQueued = DateTimeOffset.UtcNow,
|
||||
RetryCount = 0
|
||||
};
|
||||
|
||||
await _dataContext.Downloads.AddAsync(download);
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await TorrentData.VoidCache();
|
||||
|
||||
return download;
|
||||
}
|
||||
|
||||
public async Task UpdateUnrestrictedLink(Guid downloadId, String unrestrictedLink)
|
||||
{
|
||||
var dbDownload = await _dataContext.Downloads
|
||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||
|
||||
if (dbDownload == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dbDownload.Link = unrestrictedLink;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await TorrentData.VoidCache();
|
||||
}
|
||||
|
||||
public async Task UpdateDownloadStarted(Guid downloadId, DateTimeOffset? dateTime)
|
||||
{
|
||||
var dbDownload = await _dataContext.Downloads
|
||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||
|
||||
if (dbDownload == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dbDownload.DownloadStarted = dateTime;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await TorrentData.VoidCache();
|
||||
}
|
||||
|
||||
public async Task UpdateDownloadFinished(Guid downloadId, DateTimeOffset? dateTime)
|
||||
{
|
||||
var dbDownload = await _dataContext.Downloads
|
||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||
|
||||
if (dbDownload == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dbDownload.DownloadFinished = dateTime;
|
||||
dbDownload.DownloadFinished = dateTime;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await TorrentData.VoidCache();
|
||||
}
|
||||
await TorrentData.VoidCache();
|
||||
}
|
||||
|
||||
public async Task UpdateUnpackingQueued(Guid downloadId, DateTimeOffset? dateTime)
|
||||
public async Task UpdateUnpackingQueued(Guid downloadId, DateTimeOffset? dateTime)
|
||||
{
|
||||
var dbDownload = await _dataContext.Downloads
|
||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||
|
||||
if (dbDownload == null)
|
||||
{
|
||||
var dbDownload = await _dataContext.Downloads
|
||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||
|
||||
if (dbDownload == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dbDownload.UnpackingQueued = dateTime;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await TorrentData.VoidCache();
|
||||
return;
|
||||
}
|
||||
|
||||
public async Task UpdateUnpackingStarted(Guid downloadId, DateTimeOffset? dateTime)
|
||||
dbDownload.UnpackingQueued = dateTime;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await TorrentData.VoidCache();
|
||||
}
|
||||
|
||||
public async Task UpdateUnpackingStarted(Guid downloadId, DateTimeOffset? dateTime)
|
||||
{
|
||||
var dbDownload = await _dataContext.Downloads
|
||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||
|
||||
if (dbDownload == null)
|
||||
{
|
||||
var dbDownload = await _dataContext.Downloads
|
||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||
|
||||
if (dbDownload == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dbDownload.UnpackingStarted = dateTime;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await TorrentData.VoidCache();
|
||||
return;
|
||||
}
|
||||
|
||||
public async Task UpdateUnpackingFinished(Guid downloadId, DateTimeOffset? dateTime)
|
||||
dbDownload.UnpackingStarted = dateTime;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await TorrentData.VoidCache();
|
||||
}
|
||||
|
||||
public async Task UpdateUnpackingFinished(Guid downloadId, DateTimeOffset? dateTime)
|
||||
{
|
||||
var dbDownload = await _dataContext.Downloads
|
||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||
|
||||
if (dbDownload == null)
|
||||
{
|
||||
var dbDownload = await _dataContext.Downloads
|
||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||
|
||||
if (dbDownload == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dbDownload.UnpackingFinished = dateTime;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await TorrentData.VoidCache();
|
||||
return;
|
||||
}
|
||||
|
||||
dbDownload.UnpackingFinished = dateTime;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await TorrentData.VoidCache();
|
||||
}
|
||||
|
||||
public async Task UpdateCompleted(Guid downloadId, DateTimeOffset? dateTime)
|
||||
public async Task UpdateCompleted(Guid downloadId, DateTimeOffset? dateTime)
|
||||
{
|
||||
var dbDownload = await _dataContext.Downloads
|
||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||
|
||||
if (dbDownload == null)
|
||||
{
|
||||
var dbDownload = await _dataContext.Downloads
|
||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||
|
||||
if (dbDownload == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dbDownload.Completed = dateTime;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await TorrentData.VoidCache();
|
||||
return;
|
||||
}
|
||||
|
||||
public async Task UpdateError(Guid downloadId, String error)
|
||||
dbDownload.Completed = dateTime;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await TorrentData.VoidCache();
|
||||
}
|
||||
|
||||
public async Task UpdateError(Guid downloadId, String error)
|
||||
{
|
||||
var dbDownload = await _dataContext.Downloads
|
||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||
|
||||
if (dbDownload == null)
|
||||
{
|
||||
var dbDownload = await _dataContext.Downloads
|
||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||
|
||||
if (dbDownload == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dbDownload.Error = error;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await TorrentData.VoidCache();
|
||||
return;
|
||||
}
|
||||
|
||||
public async Task UpdateRetryCount(Guid downloadId, Int32 retryCount)
|
||||
dbDownload.Error = error;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await TorrentData.VoidCache();
|
||||
}
|
||||
|
||||
public async Task UpdateRetryCount(Guid downloadId, Int32 retryCount)
|
||||
{
|
||||
var dbDownload = await _dataContext.Downloads
|
||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||
|
||||
if (dbDownload == null)
|
||||
{
|
||||
var dbDownload = await _dataContext.Downloads
|
||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||
|
||||
if (dbDownload == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dbDownload.RetryCount = retryCount;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await TorrentData.VoidCache();
|
||||
return;
|
||||
}
|
||||
|
||||
public async Task UpdateRemoteId(Guid downloadId, String remoteId)
|
||||
dbDownload.RetryCount = retryCount;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await TorrentData.VoidCache();
|
||||
}
|
||||
|
||||
public async Task UpdateRemoteId(Guid downloadId, String remoteId)
|
||||
{
|
||||
var dbDownload = await _dataContext.Downloads
|
||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||
|
||||
if (dbDownload == null)
|
||||
{
|
||||
var dbDownload = await _dataContext.Downloads
|
||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||
|
||||
if (dbDownload == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dbDownload.RemoteId = remoteId;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
public async Task DeleteForTorrent(Guid torrentId)
|
||||
{
|
||||
var downloads = await _dataContext.Downloads
|
||||
.Where(m => m.TorrentId == torrentId)
|
||||
.ToListAsync();
|
||||
dbDownload.RemoteId = remoteId;
|
||||
|
||||
_dataContext.Downloads.RemoveRange(downloads);
|
||||
await _dataContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
public async Task DeleteForTorrent(Guid torrentId)
|
||||
{
|
||||
var downloads = await _dataContext.Downloads
|
||||
.Where(m => m.TorrentId == torrentId)
|
||||
.ToListAsync();
|
||||
|
||||
await TorrentData.VoidCache();
|
||||
}
|
||||
_dataContext.Downloads.RemoveRange(downloads);
|
||||
|
||||
public async Task Reset(Guid downloadId)
|
||||
{
|
||||
var dbDownload = await _dataContext.Downloads
|
||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
dbDownload.RetryCount = 0;
|
||||
dbDownload.Link = null;
|
||||
dbDownload.Added = DateTimeOffset.UtcNow;
|
||||
dbDownload.DownloadQueued = DateTimeOffset.UtcNow;
|
||||
dbDownload.DownloadStarted = null;
|
||||
dbDownload.DownloadFinished = null;
|
||||
dbDownload.UnpackingQueued = null;
|
||||
dbDownload.UnpackingStarted = null;
|
||||
dbDownload.UnpackingFinished = null;
|
||||
dbDownload.Completed = null;
|
||||
dbDownload.Error = null;
|
||||
await TorrentData.VoidCache();
|
||||
}
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
public async Task Reset(Guid downloadId)
|
||||
{
|
||||
var dbDownload = await _dataContext.Downloads
|
||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||
|
||||
await TorrentData.VoidCache();
|
||||
}
|
||||
dbDownload.RetryCount = 0;
|
||||
dbDownload.Link = null;
|
||||
dbDownload.Added = DateTimeOffset.UtcNow;
|
||||
dbDownload.DownloadQueued = DateTimeOffset.UtcNow;
|
||||
dbDownload.DownloadStarted = null;
|
||||
dbDownload.DownloadFinished = null;
|
||||
dbDownload.UnpackingQueued = null;
|
||||
dbDownload.UnpackingStarted = null;
|
||||
dbDownload.UnpackingFinished = null;
|
||||
dbDownload.Completed = null;
|
||||
dbDownload.Error = null;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await TorrentData.VoidCache();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,138 +1,132 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RdtClient.Data.Models.Data;
|
||||
using RdtClient.Data.Models.Internal;
|
||||
using Serilog;
|
||||
|
||||
namespace RdtClient.Data.Data
|
||||
namespace RdtClient.Data.Data;
|
||||
|
||||
public class SettingData
|
||||
{
|
||||
public class SettingData
|
||||
private static readonly SemaphoreSlim _settingCacheLock = new(1);
|
||||
|
||||
private readonly DataContext _dataContext;
|
||||
|
||||
public SettingData(DataContext dataContext)
|
||||
{
|
||||
private static readonly SemaphoreSlim _settingCacheLock = new(1);
|
||||
_dataContext = dataContext;
|
||||
}
|
||||
|
||||
private readonly DataContext _dataContext;
|
||||
public static DbSettings Get { get; private set; }
|
||||
|
||||
public SettingData(DataContext dataContext)
|
||||
public async Task ResetCache()
|
||||
{
|
||||
var allSettings = await _dataContext.Settings.AsNoTracking().ToListAsync();
|
||||
|
||||
String GetString(String name)
|
||||
{
|
||||
_dataContext = dataContext;
|
||||
return allSettings.FirstOrDefault(m => m.SettingId == name)?.Value;
|
||||
}
|
||||
|
||||
public static DbSettings Get { get; private set; }
|
||||
|
||||
public async Task ResetCache()
|
||||
Int32 GetInt32(String name)
|
||||
{
|
||||
var allSettings = await _dataContext.Settings.AsNoTracking().ToListAsync();
|
||||
var strVal = GetString(name);
|
||||
|
||||
String GetString(String name)
|
||||
if (!Int32.TryParse(strVal, out var intVal))
|
||||
{
|
||||
return allSettings.FirstOrDefault(m => m.SettingId == name)?.Value;
|
||||
Log.Error("Unable to parse setting {name} to Int32", name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Int32 GetInt32(String name)
|
||||
{
|
||||
var strVal = GetString(name);
|
||||
return intVal;
|
||||
|
||||
if (!Int32.TryParse(strVal, out var intVal))
|
||||
}
|
||||
|
||||
Get = new DbSettings
|
||||
{
|
||||
Provider = GetString("Provider"),
|
||||
ProviderAutoImport = GetInt32("ProviderAutoImport"),
|
||||
ProviderAutoImportCategory = GetString("ProviderAutoImportCategory"),
|
||||
ProviderAutoDelete = GetInt32("ProviderAutoDelete"),
|
||||
RealDebridApiKey = GetString("RealDebridApiKey"),
|
||||
DownloadPath = GetString("DownloadPath"),
|
||||
DownloadClient = GetString("DownloadClient"),
|
||||
TempPath = GetString("TempPath"),
|
||||
MappedPath = GetString("MappedPath"),
|
||||
DownloadLimit = GetInt32("DownloadLimit"),
|
||||
UnpackLimit = GetInt32("UnpackLimit"),
|
||||
MinFileSize = GetInt32("MinFileSize"),
|
||||
OnlyDownloadAvailableFiles = GetInt32("OnlyDownloadAvailableFiles"),
|
||||
DownloadChunkCount = GetInt32("DownloadChunkCount"),
|
||||
DownloadMaxSpeed = GetInt32("DownloadMaxSpeed"),
|
||||
ProxyServer = GetString("ProxyServer"),
|
||||
LogLevel = GetString("LogLevel"),
|
||||
Categories = GetString("Categories"),
|
||||
Aria2cUrl = GetString("Aria2cUrl"),
|
||||
Aria2cSecret = GetString("Aria2cSecret"),
|
||||
DownloadRetryAttempts = GetInt32("DownloadRetryAttempts"),
|
||||
TorrentRetryAttempts = GetInt32("TorrentRetryAttempts"),
|
||||
DeleteOnError = GetInt32("DeleteOnError"),
|
||||
TorrentLifetime = GetInt32("TorrentLifetime"),
|
||||
RunOnTorrentCompleteFileName = GetString("RunOnTorrentCompleteFileName"),
|
||||
RunOnTorrentCompleteArguments = GetString("RunOnTorrentCompleteArguments")
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<IList<Setting>> GetAll()
|
||||
{
|
||||
return await _dataContext.Settings.AsNoTracking().ToListAsync();
|
||||
}
|
||||
|
||||
public async Task Update(IList<Setting> settings)
|
||||
{
|
||||
await _settingCacheLock.WaitAsync();
|
||||
|
||||
try
|
||||
{
|
||||
var dbSettings = await _dataContext.Settings.ToListAsync();
|
||||
|
||||
foreach (var dbSetting in dbSettings)
|
||||
{
|
||||
var setting = settings.FirstOrDefault(m => m.SettingId == dbSetting.SettingId);
|
||||
|
||||
if (setting != null)
|
||||
{
|
||||
Log.Error("Unable to parse setting {name} to Int32", name);
|
||||
return 0;
|
||||
dbSetting.Value = setting.Value;
|
||||
}
|
||||
|
||||
return intVal;
|
||||
|
||||
}
|
||||
|
||||
Get = new DbSettings
|
||||
{
|
||||
Provider = GetString("Provider"),
|
||||
ProviderAutoImport = GetInt32("ProviderAutoImport"),
|
||||
ProviderAutoImportCategory = GetString("ProviderAutoImportCategory"),
|
||||
ProviderAutoDelete = GetInt32("ProviderAutoDelete"),
|
||||
RealDebridApiKey = GetString("RealDebridApiKey"),
|
||||
DownloadPath = GetString("DownloadPath"),
|
||||
DownloadClient = GetString("DownloadClient"),
|
||||
TempPath = GetString("TempPath"),
|
||||
MappedPath = GetString("MappedPath"),
|
||||
DownloadLimit = GetInt32("DownloadLimit"),
|
||||
UnpackLimit = GetInt32("UnpackLimit"),
|
||||
MinFileSize = GetInt32("MinFileSize"),
|
||||
OnlyDownloadAvailableFiles = GetInt32("OnlyDownloadAvailableFiles"),
|
||||
DownloadChunkCount = GetInt32("DownloadChunkCount"),
|
||||
DownloadMaxSpeed = GetInt32("DownloadMaxSpeed"),
|
||||
ProxyServer = GetString("ProxyServer"),
|
||||
LogLevel = GetString("LogLevel"),
|
||||
Categories = GetString("Categories"),
|
||||
Aria2cUrl = GetString("Aria2cUrl"),
|
||||
Aria2cSecret = GetString("Aria2cSecret"),
|
||||
DownloadRetryAttempts = GetInt32("DownloadRetryAttempts"),
|
||||
TorrentRetryAttempts = GetInt32("TorrentRetryAttempts"),
|
||||
DeleteOnError = GetInt32("DeleteOnError"),
|
||||
TorrentLifetime = GetInt32("TorrentLifetime"),
|
||||
RunOnTorrentCompleteFileName = GetString("RunOnTorrentCompleteFileName"),
|
||||
RunOnTorrentCompleteArguments = GetString("RunOnTorrentCompleteArguments")
|
||||
};
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await ResetCache();
|
||||
}
|
||||
|
||||
public async Task<IList<Setting>> GetAll()
|
||||
finally
|
||||
{
|
||||
return await _dataContext.Settings.AsNoTracking().ToListAsync();
|
||||
}
|
||||
|
||||
public async Task Update(IList<Setting> settings)
|
||||
{
|
||||
await _settingCacheLock.WaitAsync();
|
||||
|
||||
try
|
||||
{
|
||||
var dbSettings = await _dataContext.Settings.ToListAsync();
|
||||
|
||||
foreach (var dbSetting in dbSettings)
|
||||
{
|
||||
var setting = settings.FirstOrDefault(m => m.SettingId == dbSetting.SettingId);
|
||||
|
||||
if (setting != null)
|
||||
{
|
||||
dbSetting.Value = setting.Value;
|
||||
}
|
||||
}
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await ResetCache();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_settingCacheLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateString(String key, String value)
|
||||
{
|
||||
await _settingCacheLock.WaitAsync();
|
||||
|
||||
try
|
||||
{
|
||||
var dbSetting = await _dataContext.Settings.FirstOrDefaultAsync(m => m.SettingId == key);
|
||||
|
||||
if (dbSetting == null)
|
||||
{
|
||||
throw new Exception($"Cannot find setting with key {key}");
|
||||
}
|
||||
|
||||
dbSetting.Value = value;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await ResetCache();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_settingCacheLock.Release();
|
||||
}
|
||||
_settingCacheLock.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateString(String key, String value)
|
||||
{
|
||||
await _settingCacheLock.WaitAsync();
|
||||
|
||||
try
|
||||
{
|
||||
var dbSetting = await _dataContext.Settings.FirstOrDefaultAsync(m => m.SettingId == key);
|
||||
|
||||
if (dbSetting == null)
|
||||
{
|
||||
throw new Exception($"Cannot find setting with key {key}");
|
||||
}
|
||||
|
||||
dbSetting.Value = value;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await ResetCache();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_settingCacheLock.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,316 +1,310 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RdtClient.Data.Models.Data;
|
||||
|
||||
namespace RdtClient.Data.Data
|
||||
namespace RdtClient.Data.Data;
|
||||
|
||||
public class TorrentData
|
||||
{
|
||||
public class TorrentData
|
||||
private static IList<Torrent> _torrentCache;
|
||||
private static readonly SemaphoreSlim TorrentCacheLock = new(1, 1);
|
||||
|
||||
private readonly DataContext _dataContext;
|
||||
|
||||
public TorrentData(DataContext dataContext)
|
||||
{
|
||||
private static IList<Torrent> _torrentCache;
|
||||
private static readonly SemaphoreSlim TorrentCacheLock = new(1, 1);
|
||||
_dataContext = dataContext;
|
||||
}
|
||||
|
||||
private readonly DataContext _dataContext;
|
||||
public async Task<IList<Torrent>> Get()
|
||||
{
|
||||
await TorrentCacheLock.WaitAsync();
|
||||
|
||||
public TorrentData(DataContext dataContext)
|
||||
try
|
||||
{
|
||||
_dataContext = dataContext;
|
||||
_torrentCache ??= await _dataContext.Torrents
|
||||
.AsNoTracking()
|
||||
.Include(m => m.Downloads)
|
||||
.ToListAsync();
|
||||
|
||||
return _torrentCache.OrderBy(m => m.Priority ?? 9999).ThenBy(m => m.Added).ToList();
|
||||
}
|
||||
|
||||
public async Task<IList<Torrent>> Get()
|
||||
finally
|
||||
{
|
||||
await TorrentCacheLock.WaitAsync();
|
||||
|
||||
try
|
||||
{
|
||||
_torrentCache ??= await _dataContext.Torrents
|
||||
.AsNoTracking()
|
||||
.Include(m => m.Downloads)
|
||||
.ToListAsync();
|
||||
|
||||
return _torrentCache.OrderBy(m => m.Priority ?? 9999).ThenBy(m => m.Added).ToList();
|
||||
}
|
||||
finally
|
||||
{
|
||||
TorrentCacheLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Torrent> GetById(Guid torrentId)
|
||||
{
|
||||
var dbTorrent = await _dataContext.Torrents
|
||||
.AsNoTracking()
|
||||
.Include(m => m.Downloads)
|
||||
.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
|
||||
|
||||
if (dbTorrent == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (var file in dbTorrent.Downloads)
|
||||
{
|
||||
file.Torrent = null;
|
||||
}
|
||||
|
||||
return dbTorrent;
|
||||
}
|
||||
|
||||
public async Task<Torrent> GetByHash(String hash)
|
||||
{
|
||||
var dbTorrent = await _dataContext.Torrents
|
||||
.AsNoTracking()
|
||||
.Include(m => m.Downloads)
|
||||
.FirstOrDefaultAsync(m => m.Hash.ToLower() == hash.ToLower());
|
||||
|
||||
if (dbTorrent == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (var file in dbTorrent.Downloads)
|
||||
{
|
||||
file.Torrent = null;
|
||||
}
|
||||
|
||||
return dbTorrent;
|
||||
}
|
||||
|
||||
public async Task<Torrent> Add(String realDebridId,
|
||||
String hash,
|
||||
String fileOrMagnetContents,
|
||||
Boolean isFile,
|
||||
Torrent torrent)
|
||||
{
|
||||
var newTorrent = new Torrent
|
||||
{
|
||||
TorrentId = Guid.NewGuid(),
|
||||
Added = DateTimeOffset.UtcNow,
|
||||
RdId = realDebridId,
|
||||
Hash = hash.ToLower(),
|
||||
Category = torrent.Category,
|
||||
DownloadAction = torrent.DownloadAction,
|
||||
FinishedAction = torrent.FinishedAction,
|
||||
DownloadMinSize = torrent.DownloadMinSize,
|
||||
DownloadManualFiles = torrent.DownloadManualFiles,
|
||||
FileOrMagnet = fileOrMagnetContents,
|
||||
IsFile = isFile,
|
||||
Priority = torrent.Priority,
|
||||
TorrentRetryAttempts = torrent.TorrentRetryAttempts,
|
||||
DownloadRetryAttempts = torrent.DownloadRetryAttempts,
|
||||
DeleteOnError = torrent.DeleteOnError,
|
||||
Lifetime = torrent.Lifetime
|
||||
};
|
||||
|
||||
await _dataContext.Torrents.AddAsync(newTorrent);
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await VoidCache();
|
||||
|
||||
return newTorrent;
|
||||
}
|
||||
|
||||
public async Task UpdateRdData(Torrent torrent)
|
||||
{
|
||||
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrent.TorrentId);
|
||||
|
||||
if (dbTorrent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dbTorrent.RdName = torrent.RdName;
|
||||
dbTorrent.RdSize = torrent.RdSize;
|
||||
dbTorrent.RdHost = torrent.RdHost;
|
||||
dbTorrent.RdSplit = torrent.RdSplit;
|
||||
dbTorrent.RdProgress = torrent.RdProgress;
|
||||
dbTorrent.RdStatus = torrent.RdStatus;
|
||||
dbTorrent.RdStatusRaw = torrent.RdStatusRaw;
|
||||
dbTorrent.RdAdded = torrent.RdAdded;
|
||||
dbTorrent.RdEnded = torrent.RdEnded;
|
||||
dbTorrent.RdSpeed = torrent.RdSpeed;
|
||||
dbTorrent.RdSeeders = torrent.RdSeeders;
|
||||
|
||||
if (torrent.Files != null)
|
||||
{
|
||||
dbTorrent.RdFiles = torrent.RdFiles;
|
||||
}
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await VoidCache();
|
||||
}
|
||||
|
||||
public async Task Update(Torrent torrent)
|
||||
{
|
||||
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrent.TorrentId);
|
||||
|
||||
if (dbTorrent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dbTorrent.Priority = torrent.Priority;
|
||||
dbTorrent.DownloadRetryAttempts = torrent.DownloadRetryAttempts;
|
||||
dbTorrent.TorrentRetryAttempts = torrent.TorrentRetryAttempts;
|
||||
dbTorrent.DeleteOnError = torrent.DeleteOnError;
|
||||
dbTorrent.Lifetime = torrent.Lifetime;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await VoidCache();
|
||||
}
|
||||
|
||||
public async Task UpdateCategory(Guid torrentId, String category)
|
||||
{
|
||||
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
|
||||
|
||||
if (dbTorrent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dbTorrent.Category = category;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await VoidCache();
|
||||
}
|
||||
|
||||
public async Task UpdateComplete(Guid torrentId, String error, DateTimeOffset? datetime, Boolean retry)
|
||||
{
|
||||
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
|
||||
|
||||
if (dbTorrent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (String.IsNullOrWhiteSpace(error))
|
||||
{
|
||||
var downloads = await _dataContext.Downloads.AsNoTracking().Where(m => m.TorrentId == torrentId).ToListAsync();
|
||||
var downloadWithErrors = downloads.Where(m => !String.IsNullOrWhiteSpace(m.Error)).ToList();
|
||||
|
||||
if (downloadWithErrors.Any())
|
||||
{
|
||||
error = $"{downloadWithErrors.Count}/{downloads.Count} downloads failed with errors";
|
||||
}
|
||||
}
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(error) && retry)
|
||||
{
|
||||
if (dbTorrent.RetryCount < dbTorrent.TorrentRetryAttempts)
|
||||
{
|
||||
dbTorrent.RetryCount += 1;
|
||||
dbTorrent.Retry = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
dbTorrent.Completed = datetime;
|
||||
dbTorrent.Error = error;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await VoidCache();
|
||||
}
|
||||
|
||||
public async Task UpdateFilesSelected(Guid torrentId, DateTimeOffset datetime)
|
||||
{
|
||||
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
|
||||
|
||||
if (dbTorrent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dbTorrent.FilesSelected = datetime;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await VoidCache();
|
||||
}
|
||||
|
||||
public async Task UpdatePriority(Guid torrentId, Int32? priority)
|
||||
{
|
||||
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
|
||||
|
||||
if (dbTorrent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dbTorrent.Priority = priority;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await VoidCache();
|
||||
}
|
||||
|
||||
public async Task UpdateRetry(Guid torrentId, DateTimeOffset? dateTime, Int32 retryCount)
|
||||
{
|
||||
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
|
||||
|
||||
if (dbTorrent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dbTorrent.RetryCount = retryCount;
|
||||
dbTorrent.Retry = dateTime;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await VoidCache();
|
||||
}
|
||||
|
||||
public async Task UpdateError(Guid torrentId, String error)
|
||||
{
|
||||
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
|
||||
|
||||
if (dbTorrent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dbTorrent.Error = error;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await VoidCache();
|
||||
}
|
||||
|
||||
public async Task Delete(Guid torrentId)
|
||||
{
|
||||
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
|
||||
|
||||
if (dbTorrent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_dataContext.Torrents.Remove(dbTorrent);
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await VoidCache();
|
||||
}
|
||||
|
||||
public static async Task VoidCache()
|
||||
{
|
||||
await TorrentCacheLock.WaitAsync();
|
||||
|
||||
try
|
||||
{
|
||||
_torrentCache = null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
TorrentCacheLock.Release();
|
||||
}
|
||||
TorrentCacheLock.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Torrent> GetById(Guid torrentId)
|
||||
{
|
||||
var dbTorrent = await _dataContext.Torrents
|
||||
.AsNoTracking()
|
||||
.Include(m => m.Downloads)
|
||||
.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
|
||||
|
||||
if (dbTorrent == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (var file in dbTorrent.Downloads)
|
||||
{
|
||||
file.Torrent = null;
|
||||
}
|
||||
|
||||
return dbTorrent;
|
||||
}
|
||||
|
||||
public async Task<Torrent> GetByHash(String hash)
|
||||
{
|
||||
var dbTorrent = await _dataContext.Torrents
|
||||
.AsNoTracking()
|
||||
.Include(m => m.Downloads)
|
||||
.FirstOrDefaultAsync(m => m.Hash.ToLower() == hash.ToLower());
|
||||
|
||||
if (dbTorrent == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (var file in dbTorrent.Downloads)
|
||||
{
|
||||
file.Torrent = null;
|
||||
}
|
||||
|
||||
return dbTorrent;
|
||||
}
|
||||
|
||||
public async Task<Torrent> Add(String realDebridId,
|
||||
String hash,
|
||||
String fileOrMagnetContents,
|
||||
Boolean isFile,
|
||||
Torrent torrent)
|
||||
{
|
||||
var newTorrent = new Torrent
|
||||
{
|
||||
TorrentId = Guid.NewGuid(),
|
||||
Added = DateTimeOffset.UtcNow,
|
||||
RdId = realDebridId,
|
||||
Hash = hash.ToLower(),
|
||||
Category = torrent.Category,
|
||||
DownloadAction = torrent.DownloadAction,
|
||||
FinishedAction = torrent.FinishedAction,
|
||||
DownloadMinSize = torrent.DownloadMinSize,
|
||||
DownloadManualFiles = torrent.DownloadManualFiles,
|
||||
FileOrMagnet = fileOrMagnetContents,
|
||||
IsFile = isFile,
|
||||
Priority = torrent.Priority,
|
||||
TorrentRetryAttempts = torrent.TorrentRetryAttempts,
|
||||
DownloadRetryAttempts = torrent.DownloadRetryAttempts,
|
||||
DeleteOnError = torrent.DeleteOnError,
|
||||
Lifetime = torrent.Lifetime
|
||||
};
|
||||
|
||||
await _dataContext.Torrents.AddAsync(newTorrent);
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await VoidCache();
|
||||
|
||||
return newTorrent;
|
||||
}
|
||||
|
||||
public async Task UpdateRdData(Torrent torrent)
|
||||
{
|
||||
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrent.TorrentId);
|
||||
|
||||
if (dbTorrent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dbTorrent.RdName = torrent.RdName;
|
||||
dbTorrent.RdSize = torrent.RdSize;
|
||||
dbTorrent.RdHost = torrent.RdHost;
|
||||
dbTorrent.RdSplit = torrent.RdSplit;
|
||||
dbTorrent.RdProgress = torrent.RdProgress;
|
||||
dbTorrent.RdStatus = torrent.RdStatus;
|
||||
dbTorrent.RdStatusRaw = torrent.RdStatusRaw;
|
||||
dbTorrent.RdAdded = torrent.RdAdded;
|
||||
dbTorrent.RdEnded = torrent.RdEnded;
|
||||
dbTorrent.RdSpeed = torrent.RdSpeed;
|
||||
dbTorrent.RdSeeders = torrent.RdSeeders;
|
||||
|
||||
if (torrent.Files != null)
|
||||
{
|
||||
dbTorrent.RdFiles = torrent.RdFiles;
|
||||
}
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await VoidCache();
|
||||
}
|
||||
|
||||
public async Task Update(Torrent torrent)
|
||||
{
|
||||
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrent.TorrentId);
|
||||
|
||||
if (dbTorrent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dbTorrent.Priority = torrent.Priority;
|
||||
dbTorrent.DownloadRetryAttempts = torrent.DownloadRetryAttempts;
|
||||
dbTorrent.TorrentRetryAttempts = torrent.TorrentRetryAttempts;
|
||||
dbTorrent.DeleteOnError = torrent.DeleteOnError;
|
||||
dbTorrent.Lifetime = torrent.Lifetime;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await VoidCache();
|
||||
}
|
||||
|
||||
public async Task UpdateCategory(Guid torrentId, String category)
|
||||
{
|
||||
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
|
||||
|
||||
if (dbTorrent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dbTorrent.Category = category;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await VoidCache();
|
||||
}
|
||||
|
||||
public async Task UpdateComplete(Guid torrentId, String error, DateTimeOffset? datetime, Boolean retry)
|
||||
{
|
||||
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
|
||||
|
||||
if (dbTorrent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (String.IsNullOrWhiteSpace(error))
|
||||
{
|
||||
var downloads = await _dataContext.Downloads.AsNoTracking().Where(m => m.TorrentId == torrentId).ToListAsync();
|
||||
var downloadWithErrors = downloads.Where(m => !String.IsNullOrWhiteSpace(m.Error)).ToList();
|
||||
|
||||
if (downloadWithErrors.Any())
|
||||
{
|
||||
error = $"{downloadWithErrors.Count}/{downloads.Count} downloads failed with errors";
|
||||
}
|
||||
}
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(error) && retry)
|
||||
{
|
||||
if (dbTorrent.RetryCount < dbTorrent.TorrentRetryAttempts)
|
||||
{
|
||||
dbTorrent.RetryCount += 1;
|
||||
dbTorrent.Retry = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
dbTorrent.Completed = datetime;
|
||||
dbTorrent.Error = error;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await VoidCache();
|
||||
}
|
||||
|
||||
public async Task UpdateFilesSelected(Guid torrentId, DateTimeOffset datetime)
|
||||
{
|
||||
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
|
||||
|
||||
if (dbTorrent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dbTorrent.FilesSelected = datetime;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await VoidCache();
|
||||
}
|
||||
|
||||
public async Task UpdatePriority(Guid torrentId, Int32? priority)
|
||||
{
|
||||
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
|
||||
|
||||
if (dbTorrent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dbTorrent.Priority = priority;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await VoidCache();
|
||||
}
|
||||
|
||||
public async Task UpdateRetry(Guid torrentId, DateTimeOffset? dateTime, Int32 retryCount)
|
||||
{
|
||||
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
|
||||
|
||||
if (dbTorrent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dbTorrent.RetryCount = retryCount;
|
||||
dbTorrent.Retry = dateTime;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await VoidCache();
|
||||
}
|
||||
|
||||
public async Task UpdateError(Guid torrentId, String error)
|
||||
{
|
||||
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
|
||||
|
||||
if (dbTorrent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dbTorrent.Error = error;
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await VoidCache();
|
||||
}
|
||||
|
||||
public async Task Delete(Guid torrentId)
|
||||
{
|
||||
var dbTorrent = await _dataContext.Torrents.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
|
||||
|
||||
if (dbTorrent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_dataContext.Torrents.Remove(dbTorrent);
|
||||
|
||||
await _dataContext.SaveChangesAsync();
|
||||
|
||||
await VoidCache();
|
||||
}
|
||||
|
||||
public static async Task VoidCache()
|
||||
{
|
||||
await TorrentCacheLock.WaitAsync();
|
||||
|
||||
try
|
||||
{
|
||||
_torrentCache = null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
TorrentCacheLock.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +1,19 @@
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace RdtClient.Data.Data
|
||||
namespace RdtClient.Data.Data;
|
||||
|
||||
public class UserData
|
||||
{
|
||||
public class UserData
|
||||
private readonly DataContext _dataContext;
|
||||
|
||||
public UserData(DataContext dataContext)
|
||||
{
|
||||
private readonly DataContext _dataContext;
|
||||
|
||||
public UserData(DataContext dataContext)
|
||||
{
|
||||
_dataContext = dataContext;
|
||||
}
|
||||
|
||||
public async Task<IdentityUser> GetUser()
|
||||
{
|
||||
return await _dataContext.Users.FirstOrDefaultAsync();
|
||||
}
|
||||
_dataContext = dataContext;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IdentityUser> GetUser()
|
||||
{
|
||||
return await _dataContext.Users.FirstOrDefaultAsync();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,20 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using RdtClient.Data.Data;
|
||||
using RdtClient.Data.Models.Internal;
|
||||
|
||||
namespace RdtClient.Data
|
||||
namespace RdtClient.Data;
|
||||
|
||||
public static class DiConfig
|
||||
{
|
||||
public static class DiConfig
|
||||
public static void Config(IServiceCollection services, AppSettings appSettings)
|
||||
{
|
||||
public static void Config(IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<DownloadData>();
|
||||
services.AddScoped<SettingData>();
|
||||
services.AddScoped<TorrentData>();
|
||||
services.AddScoped<UserData>();
|
||||
}
|
||||
var connectionString = $"Data Source={appSettings.Database.Path}";
|
||||
services.AddDbContext<DataContext>(options => options.UseSqlite(connectionString));
|
||||
|
||||
services.AddScoped<DownloadData>();
|
||||
services.AddScoped<SettingData>();
|
||||
services.AddScoped<TorrentData>();
|
||||
services.AddScoped<UserData>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,8 @@
|
|||
namespace RdtClient.Data.Enums
|
||||
namespace RdtClient.Data.Enums;
|
||||
|
||||
public enum TorrentDownloadAction
|
||||
{
|
||||
public enum TorrentDownloadAction
|
||||
{
|
||||
DownloadAll = 0,
|
||||
DownloadAvailableFiles = 1,
|
||||
DownloadManual = 2
|
||||
}
|
||||
}
|
||||
DownloadAll = 0,
|
||||
DownloadAvailableFiles = 1,
|
||||
DownloadManual = 2
|
||||
}
|
||||
|
|
@ -1,9 +1,8 @@
|
|||
namespace RdtClient.Data.Enums
|
||||
namespace RdtClient.Data.Enums;
|
||||
|
||||
public enum TorrentFinishedAction
|
||||
{
|
||||
public enum TorrentFinishedAction
|
||||
{
|
||||
None = 0,
|
||||
RemoveAllTorrents = 1,
|
||||
RemoveRealDebrid = 2
|
||||
}
|
||||
}
|
||||
None = 0,
|
||||
RemoveAllTorrents = 1,
|
||||
RemoveRealDebrid = 2
|
||||
}
|
||||
|
|
@ -1,13 +1,12 @@
|
|||
namespace RdtClient.Data.Enums
|
||||
{
|
||||
public enum TorrentStatus
|
||||
{
|
||||
Processing = 0,
|
||||
WaitingForFileSelection = 1,
|
||||
Downloading = 2,
|
||||
Finished = 3,
|
||||
Uploading = 4,
|
||||
namespace RdtClient.Data.Enums;
|
||||
|
||||
Error = 99
|
||||
}
|
||||
}
|
||||
public enum TorrentStatus
|
||||
{
|
||||
Processing = 0,
|
||||
WaitingForFileSelection = 1,
|
||||
Downloading = 2,
|
||||
Finished = 3,
|
||||
Uploading = 4,
|
||||
|
||||
Error = 99
|
||||
}
|
||||
|
|
@ -1,44 +1,42 @@
|
|||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace RdtClient.Data.Models.Data
|
||||
namespace RdtClient.Data.Models.Data;
|
||||
|
||||
public class Download
|
||||
{
|
||||
public class Download
|
||||
{
|
||||
[Key]
|
||||
public Guid DownloadId { get; set; }
|
||||
[Key]
|
||||
public Guid DownloadId { get; set; }
|
||||
|
||||
public Guid TorrentId { get; set; }
|
||||
public Guid TorrentId { get; set; }
|
||||
|
||||
[ForeignKey("TorrentId")]
|
||||
public Torrent Torrent { get; set; }
|
||||
[ForeignKey("TorrentId")]
|
||||
public Torrent Torrent { get; set; }
|
||||
|
||||
public String Path { get; set; }
|
||||
public String Link { get; set; }
|
||||
public String Path { get; set; }
|
||||
public String Link { get; set; }
|
||||
|
||||
public DateTimeOffset Added { get; set; }
|
||||
public DateTimeOffset? DownloadQueued { get; set; }
|
||||
public DateTimeOffset? DownloadStarted { get; set; }
|
||||
public DateTimeOffset? DownloadFinished { get; set; }
|
||||
public DateTimeOffset? UnpackingQueued { get; set; }
|
||||
public DateTimeOffset? UnpackingStarted { get; set; }
|
||||
public DateTimeOffset? UnpackingFinished { get; set; }
|
||||
public DateTimeOffset? Completed { get; set; }
|
||||
public DateTimeOffset Added { get; set; }
|
||||
public DateTimeOffset? DownloadQueued { get; set; }
|
||||
public DateTimeOffset? DownloadStarted { get; set; }
|
||||
public DateTimeOffset? DownloadFinished { get; set; }
|
||||
public DateTimeOffset? UnpackingQueued { get; set; }
|
||||
public DateTimeOffset? UnpackingStarted { get; set; }
|
||||
public DateTimeOffset? UnpackingFinished { get; set; }
|
||||
public DateTimeOffset? Completed { get; set; }
|
||||
|
||||
public Int32 RetryCount { get; set; }
|
||||
public Int32 RetryCount { get; set; }
|
||||
|
||||
public String Error { get; set; }
|
||||
public String Error { get; set; }
|
||||
|
||||
public String RemoteId { get; set; }
|
||||
public String RemoteId { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public Int64 BytesTotal { get; set; }
|
||||
[NotMapped]
|
||||
public Int64 BytesTotal { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public Int64 BytesDone { get; set; }
|
||||
[NotMapped]
|
||||
public Int64 BytesDone { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public Int64 Speed { get; set; }
|
||||
}
|
||||
}
|
||||
[NotMapped]
|
||||
public Int64 Speed { get; set; }
|
||||
}
|
||||
|
|
@ -1,15 +1,13 @@
|
|||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace RdtClient.Data.Models.Data
|
||||
namespace RdtClient.Data.Models.Data;
|
||||
|
||||
public class Setting
|
||||
{
|
||||
public class Setting
|
||||
{
|
||||
[Key]
|
||||
public String SettingId { get; set; }
|
||||
[Key]
|
||||
public String SettingId { get; set; }
|
||||
|
||||
public String Value { get; set; }
|
||||
public String Value { get; set; }
|
||||
|
||||
public String Type { get; set; }
|
||||
}
|
||||
}
|
||||
public String Type { get; set; }
|
||||
}
|
||||
|
|
@ -1,94 +1,91 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text.Json;
|
||||
using RdtClient.Data.Enums;
|
||||
using RdtClient.Data.Models.TorrentClient;
|
||||
|
||||
namespace RdtClient.Data.Models.Data
|
||||
namespace RdtClient.Data.Models.Data;
|
||||
|
||||
public class Torrent
|
||||
{
|
||||
public class Torrent
|
||||
{
|
||||
[Key]
|
||||
public Guid TorrentId { get; set; }
|
||||
[Key]
|
||||
public Guid TorrentId { get; set; }
|
||||
|
||||
public String Hash { get; set; }
|
||||
public String Hash { get; set; }
|
||||
|
||||
public String Category { get; set; }
|
||||
public String Category { get; set; }
|
||||
|
||||
public TorrentDownloadAction DownloadAction { get; set; }
|
||||
public TorrentFinishedAction FinishedAction { get; set; }
|
||||
public Int32 DownloadMinSize { get; set; }
|
||||
public String DownloadManualFiles { get; set; }
|
||||
public TorrentDownloadAction DownloadAction { get; set; }
|
||||
public TorrentFinishedAction FinishedAction { get; set; }
|
||||
public Int32 DownloadMinSize { get; set; }
|
||||
public String DownloadManualFiles { get; set; }
|
||||
|
||||
public DateTimeOffset Added { get; set; }
|
||||
public DateTimeOffset? FilesSelected { get; set; }
|
||||
public DateTimeOffset? Completed { get; set; }
|
||||
public DateTimeOffset? Retry { get; set; }
|
||||
public DateTimeOffset Added { get; set; }
|
||||
public DateTimeOffset? FilesSelected { get; set; }
|
||||
public DateTimeOffset? Completed { get; set; }
|
||||
public DateTimeOffset? Retry { get; set; }
|
||||
|
||||
public String FileOrMagnet { get; set; }
|
||||
public Boolean IsFile { get; set; }
|
||||
public String FileOrMagnet { get; set; }
|
||||
public Boolean IsFile { get; set; }
|
||||
|
||||
public Int32? Priority { get; set; }
|
||||
public Int32 RetryCount { get; set; }
|
||||
public Int32 DownloadRetryAttempts { get; set; }
|
||||
public Int32 TorrentRetryAttempts { get; set; }
|
||||
public Int32 DeleteOnError { get; set; }
|
||||
public Int32 Lifetime { get; set; }
|
||||
public Int32? Priority { get; set; }
|
||||
public Int32 RetryCount { get; set; }
|
||||
public Int32 DownloadRetryAttempts { get; set; }
|
||||
public Int32 TorrentRetryAttempts { get; set; }
|
||||
public Int32 DeleteOnError { get; set; }
|
||||
public Int32 Lifetime { get; set; }
|
||||
|
||||
public String Error { get; set; }
|
||||
public String Error { get; set; }
|
||||
|
||||
[InverseProperty("Torrent")]
|
||||
public IList<Download> Downloads { get; set; }
|
||||
[InverseProperty("Torrent")]
|
||||
public IList<Download> Downloads { get; set; }
|
||||
|
||||
public String RdId { get; set; }
|
||||
public String RdName { get; set; }
|
||||
public Int64 RdSize { get; set; }
|
||||
public String RdHost { get; set; }
|
||||
public Int64 RdSplit { get; set; }
|
||||
public Int64 RdProgress { get; set; }
|
||||
public TorrentStatus RdStatus { get; set; }
|
||||
public String RdStatusRaw { get; set; }
|
||||
public DateTimeOffset RdAdded { get; set; }
|
||||
public DateTimeOffset? RdEnded { get; set; }
|
||||
public Int64? RdSpeed { get; set; }
|
||||
public Int64? RdSeeders { get; set; }
|
||||
public String RdFiles { get; set; }
|
||||
public String RdId { get; set; }
|
||||
public String RdName { get; set; }
|
||||
public Int64 RdSize { get; set; }
|
||||
public String RdHost { get; set; }
|
||||
public Int64 RdSplit { get; set; }
|
||||
public Int64 RdProgress { get; set; }
|
||||
public TorrentStatus RdStatus { get; set; }
|
||||
public String RdStatusRaw { get; set; }
|
||||
public DateTimeOffset RdAdded { get; set; }
|
||||
public DateTimeOffset? RdEnded { get; set; }
|
||||
public Int64? RdSpeed { get; set; }
|
||||
public Int64? RdSeeders { get; set; }
|
||||
public String RdFiles { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public IList<TorrentClientFile> Files
|
||||
[NotMapped]
|
||||
public IList<TorrentClientFile> Files
|
||||
{
|
||||
get
|
||||
{
|
||||
get
|
||||
if (String.IsNullOrWhiteSpace(RdFiles))
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(RdFiles))
|
||||
{
|
||||
return new List<TorrentClientFile>();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return JsonSerializer.Deserialize<List<TorrentClientFile>>(RdFiles);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new List<TorrentClientFile>();
|
||||
}
|
||||
return new List<TorrentClientFile>();
|
||||
}
|
||||
}
|
||||
|
||||
[NotMapped]
|
||||
public IList<String> ManualFiles
|
||||
{
|
||||
get
|
||||
try
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(DownloadManualFiles))
|
||||
{
|
||||
return new List<String>();
|
||||
}
|
||||
|
||||
return DownloadManualFiles.Split(",");
|
||||
return JsonSerializer.Deserialize<List<TorrentClientFile>>(RdFiles);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new List<TorrentClientFile>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[NotMapped]
|
||||
public IList<String> ManualFiles
|
||||
{
|
||||
get
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(DownloadManualFiles))
|
||||
{
|
||||
return new List<String>();
|
||||
}
|
||||
|
||||
return DownloadManualFiles.Split(",");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +1,26 @@
|
|||
using System;
|
||||
namespace RdtClient.Data.Models.Internal;
|
||||
|
||||
namespace RdtClient.Data.Models.Internal
|
||||
public class AppSettings
|
||||
{
|
||||
public class AppSettings
|
||||
{
|
||||
public AppSettingsLogging Logging { get; set; }
|
||||
public AppSettingsDatabase Database { get; set; }
|
||||
public AppSettingsLogging Logging { get; set; }
|
||||
public AppSettingsDatabase Database { get; set; }
|
||||
|
||||
public String AllowedHosts { get; set; }
|
||||
public String HostUrl { get; set; }
|
||||
}
|
||||
|
||||
public class AppSettingsLogging
|
||||
{
|
||||
public AppSettingsLoggingFile File { get; set; }
|
||||
}
|
||||
|
||||
public class AppSettingsLoggingFile
|
||||
{
|
||||
public String Path { get; set; }
|
||||
public String Append { get; set; }
|
||||
public Int64 FileSizeLimitBytes { get; set; }
|
||||
public Int32 MaxRollingFiles { get; set; }
|
||||
}
|
||||
|
||||
public class AppSettingsDatabase
|
||||
{
|
||||
public String Path { get; set; }
|
||||
}
|
||||
public Int32 Port { get; set; }
|
||||
}
|
||||
|
||||
public class AppSettingsLogging
|
||||
{
|
||||
public AppSettingsLoggingFile File { get; set; }
|
||||
}
|
||||
|
||||
public class AppSettingsLoggingFile
|
||||
{
|
||||
public String Path { get; set; }
|
||||
public Int64 FileSizeLimitBytes { get; set; }
|
||||
public Int32 MaxRollingFiles { get; set; }
|
||||
}
|
||||
|
||||
public class AppSettingsDatabase
|
||||
{
|
||||
public String Path { get; set; }
|
||||
}
|
||||
|
|
@ -1,34 +1,31 @@
|
|||
using System;
|
||||
namespace RdtClient.Data.Models.Internal;
|
||||
|
||||
namespace RdtClient.Data.Models.Internal
|
||||
public class DbSettings
|
||||
{
|
||||
public class DbSettings
|
||||
{
|
||||
public String Provider { get; set; }
|
||||
public Int32 ProviderAutoImport { get; set; }
|
||||
public String ProviderAutoImportCategory { get; set; }
|
||||
public Int32 ProviderAutoDelete { get; set; }
|
||||
public String RealDebridApiKey { get; set; }
|
||||
public String DownloadPath { get; set; }
|
||||
public String DownloadClient { get; set; }
|
||||
public String TempPath { get; set; }
|
||||
public String MappedPath { get; set; }
|
||||
public Int32 DownloadLimit { get; set; }
|
||||
public Int32 UnpackLimit { get; set; }
|
||||
public Int32 MinFileSize { get; set; }
|
||||
public Int32 OnlyDownloadAvailableFiles { get; set; }
|
||||
public Int32 DownloadChunkCount { get; set; }
|
||||
public Int32 DownloadMaxSpeed { get; set; }
|
||||
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; }
|
||||
public Int32 DownloadRetryAttempts { get; set; }
|
||||
public Int32 TorrentRetryAttempts { get; set; }
|
||||
public Int32 DeleteOnError { get; set; }
|
||||
public Int32 TorrentLifetime { get; set; }
|
||||
public String RunOnTorrentCompleteFileName { get; set; }
|
||||
public String RunOnTorrentCompleteArguments { get; set; }
|
||||
}
|
||||
}
|
||||
public String Provider { get; set; }
|
||||
public Int32 ProviderAutoImport { get; set; }
|
||||
public String ProviderAutoImportCategory { get; set; }
|
||||
public Int32 ProviderAutoDelete { get; set; }
|
||||
public String RealDebridApiKey { get; set; }
|
||||
public String DownloadPath { get; set; }
|
||||
public String DownloadClient { get; set; }
|
||||
public String TempPath { get; set; }
|
||||
public String MappedPath { get; set; }
|
||||
public Int32 DownloadLimit { get; set; }
|
||||
public Int32 UnpackLimit { get; set; }
|
||||
public Int32 MinFileSize { get; set; }
|
||||
public Int32 OnlyDownloadAvailableFiles { get; set; }
|
||||
public Int32 DownloadChunkCount { get; set; }
|
||||
public Int32 DownloadMaxSpeed { get; set; }
|
||||
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; }
|
||||
public Int32 DownloadRetryAttempts { get; set; }
|
||||
public Int32 TorrentRetryAttempts { get; set; }
|
||||
public Int32 DeleteOnError { get; set; }
|
||||
public Int32 TorrentLifetime { get; set; }
|
||||
public String RunOnTorrentCompleteFileName { get; set; }
|
||||
public String RunOnTorrentCompleteArguments { get; set; }
|
||||
}
|
||||
|
|
@ -1,13 +1,10 @@
|
|||
using System;
|
||||
namespace RdtClient.Data.Models.Internal;
|
||||
|
||||
namespace RdtClient.Data.Models.Internal
|
||||
public class Profile
|
||||
{
|
||||
public class Profile
|
||||
{
|
||||
public String Provider { get; set; }
|
||||
public String UserName { get; set; }
|
||||
public DateTimeOffset? Expiration { get; set; }
|
||||
public String CurrentVersion { get; set; }
|
||||
public String LatestVersion { get; set; }
|
||||
}
|
||||
}
|
||||
public String Provider { get; set; }
|
||||
public String UserName { get; set; }
|
||||
public DateTimeOffset? Expiration { get; set; }
|
||||
public String CurrentVersion { get; set; }
|
||||
public String LatestVersion { get; set; }
|
||||
}
|
||||
|
|
@ -1,26 +1,24 @@
|
|||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace RdtClient.Data.Models.QBittorrent
|
||||
namespace RdtClient.Data.Models.QBittorrent;
|
||||
|
||||
public class AppBuildInfo
|
||||
{
|
||||
public class AppBuildInfo
|
||||
{
|
||||
[JsonPropertyName("bitness")]
|
||||
public Int64 Bitness { get; set; }
|
||||
[JsonPropertyName("bitness")]
|
||||
public Int64 Bitness { get; set; }
|
||||
|
||||
[JsonPropertyName("boost")]
|
||||
public String Boost { get; set; }
|
||||
[JsonPropertyName("boost")]
|
||||
public String Boost { get; set; }
|
||||
|
||||
[JsonPropertyName("libtorrent")]
|
||||
public String Libtorrent { get; set; }
|
||||
[JsonPropertyName("libtorrent")]
|
||||
public String Libtorrent { get; set; }
|
||||
|
||||
[JsonPropertyName("openssl")]
|
||||
public String Openssl { get; set; }
|
||||
[JsonPropertyName("openssl")]
|
||||
public String Openssl { get; set; }
|
||||
|
||||
[JsonPropertyName("qt")]
|
||||
public String Qt { get; set; }
|
||||
[JsonPropertyName("qt")]
|
||||
public String Qt { get; set; }
|
||||
|
||||
[JsonPropertyName("zlib")]
|
||||
public String Zlib { get; set; }
|
||||
}
|
||||
[JsonPropertyName("zlib")]
|
||||
public String Zlib { get; set; }
|
||||
}
|
||||
|
|
@ -1,435 +1,430 @@
|
|||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace RdtClient.Data.Models.QBittorrent
|
||||
namespace RdtClient.Data.Models.QBittorrent;
|
||||
|
||||
public class AppPreferences
|
||||
{
|
||||
namespace QuickType
|
||||
{
|
||||
public class AppPreferences
|
||||
{
|
||||
[JsonPropertyName("add_trackers")]
|
||||
public String AddTrackers { get; set; }
|
||||
[JsonPropertyName("add_trackers")]
|
||||
public String AddTrackers { get; set; }
|
||||
|
||||
[JsonPropertyName("add_trackers_enabled")]
|
||||
public Boolean AddTrackersEnabled { get; set; }
|
||||
[JsonPropertyName("add_trackers_enabled")]
|
||||
public Boolean AddTrackersEnabled { get; set; }
|
||||
|
||||
[JsonPropertyName("alt_dl_limit")]
|
||||
public Int64 AltDlLimit { get; set; }
|
||||
[JsonPropertyName("alt_dl_limit")]
|
||||
public Int64 AltDlLimit { get; set; }
|
||||
|
||||
[JsonPropertyName("alt_up_limit")]
|
||||
public Int64 AltUpLimit { get; set; }
|
||||
[JsonPropertyName("alt_up_limit")]
|
||||
public Int64 AltUpLimit { get; set; }
|
||||
|
||||
[JsonPropertyName("alternative_webui_enabled")]
|
||||
public Boolean AlternativeWebuiEnabled { get; set; }
|
||||
[JsonPropertyName("alternative_webui_enabled")]
|
||||
public Boolean AlternativeWebuiEnabled { get; set; }
|
||||
|
||||
[JsonPropertyName("alternative_webui_path")]
|
||||
public String AlternativeWebuiPath { get; set; }
|
||||
[JsonPropertyName("alternative_webui_path")]
|
||||
public String AlternativeWebuiPath { get; set; }
|
||||
|
||||
[JsonPropertyName("announce_ip")]
|
||||
public String AnnounceIp { get; set; }
|
||||
[JsonPropertyName("announce_ip")]
|
||||
public String AnnounceIp { get; set; }
|
||||
|
||||
[JsonPropertyName("announce_to_all_tiers")]
|
||||
public Boolean AnnounceToAllTiers { get; set; }
|
||||
[JsonPropertyName("announce_to_all_tiers")]
|
||||
public Boolean AnnounceToAllTiers { get; set; }
|
||||
|
||||
[JsonPropertyName("announce_to_all_trackers")]
|
||||
public Boolean AnnounceToAllTrackers { get; set; }
|
||||
[JsonPropertyName("announce_to_all_trackers")]
|
||||
public Boolean AnnounceToAllTrackers { get; set; }
|
||||
|
||||
[JsonPropertyName("anonymous_mode")]
|
||||
public Boolean AnonymousMode { get; set; }
|
||||
[JsonPropertyName("anonymous_mode")]
|
||||
public Boolean AnonymousMode { get; set; }
|
||||
|
||||
[JsonPropertyName("async_io_threads")]
|
||||
public Int64 AsyncIoThreads { get; set; }
|
||||
[JsonPropertyName("async_io_threads")]
|
||||
public Int64 AsyncIoThreads { get; set; }
|
||||
|
||||
[JsonPropertyName("auto_delete_mode")]
|
||||
public Int64 AutoDeleteMode { get; set; }
|
||||
[JsonPropertyName("auto_delete_mode")]
|
||||
public Int64 AutoDeleteMode { get; set; }
|
||||
|
||||
[JsonPropertyName("auto_tmm_enabled")]
|
||||
public Boolean AutoTmmEnabled { get; set; }
|
||||
[JsonPropertyName("auto_tmm_enabled")]
|
||||
public Boolean AutoTmmEnabled { get; set; }
|
||||
|
||||
[JsonPropertyName("autorun_enabled")]
|
||||
public Boolean AutorunEnabled { get; set; }
|
||||
[JsonPropertyName("autorun_enabled")]
|
||||
public Boolean AutorunEnabled { get; set; }
|
||||
|
||||
[JsonPropertyName("autorun_program")]
|
||||
public String AutorunProgram { get; set; }
|
||||
[JsonPropertyName("autorun_program")]
|
||||
public String AutorunProgram { get; set; }
|
||||
|
||||
[JsonPropertyName("banned_IPs")]
|
||||
public String BannedIPs { get; set; }
|
||||
[JsonPropertyName("banned_IPs")]
|
||||
public String BannedIPs { get; set; }
|
||||
|
||||
[JsonPropertyName("bittorrent_protocol")]
|
||||
public Int64 BittorrentProtocol { get; set; }
|
||||
[JsonPropertyName("bittorrent_protocol")]
|
||||
public Int64 BittorrentProtocol { get; set; }
|
||||
|
||||
[JsonPropertyName("bypass_auth_subnet_whitelist")]
|
||||
public String BypassAuthSubnetWhitelist { get; set; }
|
||||
[JsonPropertyName("bypass_auth_subnet_whitelist")]
|
||||
public String BypassAuthSubnetWhitelist { get; set; }
|
||||
|
||||
[JsonPropertyName("bypass_auth_subnet_whitelist_enabled")]
|
||||
public Boolean BypassAuthSubnetWhitelistEnabled { get; set; }
|
||||
[JsonPropertyName("bypass_auth_subnet_whitelist_enabled")]
|
||||
public Boolean BypassAuthSubnetWhitelistEnabled { get; set; }
|
||||
|
||||
[JsonPropertyName("bypass_local_auth")]
|
||||
public Boolean BypassLocalAuth { get; set; }
|
||||
[JsonPropertyName("bypass_local_auth")]
|
||||
public Boolean BypassLocalAuth { get; set; }
|
||||
|
||||
[JsonPropertyName("category_changed_tmm_enabled")]
|
||||
public Boolean CategoryChangedTmmEnabled { get; set; }
|
||||
[JsonPropertyName("category_changed_tmm_enabled")]
|
||||
public Boolean CategoryChangedTmmEnabled { get; set; }
|
||||
|
||||
[JsonPropertyName("checking_memory_use")]
|
||||
public Int64 CheckingMemoryUse { get; set; }
|
||||
[JsonPropertyName("checking_memory_use")]
|
||||
public Int64 CheckingMemoryUse { get; set; }
|
||||
|
||||
[JsonPropertyName("create_subfolder_enabled")]
|
||||
public Boolean CreateSubfolderEnabled { get; set; }
|
||||
[JsonPropertyName("create_subfolder_enabled")]
|
||||
public Boolean CreateSubfolderEnabled { get; set; }
|
||||
|
||||
[JsonPropertyName("current_interface_address")]
|
||||
public String CurrentInterfaceAddress { get; set; }
|
||||
[JsonPropertyName("current_interface_address")]
|
||||
public String CurrentInterfaceAddress { get; set; }
|
||||
|
||||
[JsonPropertyName("current_network_interface")]
|
||||
public String CurrentNetworkInterface { get; set; }
|
||||
[JsonPropertyName("current_network_interface")]
|
||||
public String CurrentNetworkInterface { get; set; }
|
||||
|
||||
[JsonPropertyName("dht")]
|
||||
public Boolean Dht { get; set; }
|
||||
[JsonPropertyName("dht")]
|
||||
public Boolean Dht { get; set; }
|
||||
|
||||
[JsonPropertyName("disk_cache")]
|
||||
public Int64 DiskCache { get; set; }
|
||||
[JsonPropertyName("disk_cache")]
|
||||
public Int64 DiskCache { get; set; }
|
||||
|
||||
[JsonPropertyName("disk_cache_ttl")]
|
||||
public Int64 DiskCacheTtl { get; set; }
|
||||
[JsonPropertyName("disk_cache_ttl")]
|
||||
public Int64 DiskCacheTtl { get; set; }
|
||||
|
||||
[JsonPropertyName("dl_limit")]
|
||||
public Int64 DlLimit { get; set; }
|
||||
[JsonPropertyName("dl_limit")]
|
||||
public Int64 DlLimit { get; set; }
|
||||
|
||||
[JsonPropertyName("dont_count_slow_torrents")]
|
||||
public Boolean DontCountSlowTorrents { get; set; }
|
||||
[JsonPropertyName("dont_count_slow_torrents")]
|
||||
public Boolean DontCountSlowTorrents { get; set; }
|
||||
|
||||
[JsonPropertyName("dyndns_domain")]
|
||||
public String DyndnsDomain { get; set; }
|
||||
[JsonPropertyName("dyndns_domain")]
|
||||
public String DyndnsDomain { get; set; }
|
||||
|
||||
[JsonPropertyName("dyndns_enabled")]
|
||||
public Boolean DyndnsEnabled { get; set; }
|
||||
[JsonPropertyName("dyndns_enabled")]
|
||||
public Boolean DyndnsEnabled { get; set; }
|
||||
|
||||
[JsonPropertyName("dyndns_password")]
|
||||
public String DyndnsPassword { get; set; }
|
||||
[JsonPropertyName("dyndns_password")]
|
||||
public String DyndnsPassword { get; set; }
|
||||
|
||||
[JsonPropertyName("dyndns_service")]
|
||||
public Int64 DyndnsService { get; set; }
|
||||
[JsonPropertyName("dyndns_service")]
|
||||
public Int64 DyndnsService { get; set; }
|
||||
|
||||
[JsonPropertyName("dyndns_username")]
|
||||
public String DyndnsUsername { get; set; }
|
||||
[JsonPropertyName("dyndns_username")]
|
||||
public String DyndnsUsername { get; set; }
|
||||
|
||||
[JsonPropertyName("embedded_tracker_port")]
|
||||
public Int64 EmbeddedTrackerPort { get; set; }
|
||||
[JsonPropertyName("embedded_tracker_port")]
|
||||
public Int64 EmbeddedTrackerPort { get; set; }
|
||||
|
||||
[JsonPropertyName("enable_coalesce_read_write")]
|
||||
public Boolean EnableCoalesceReadWrite { get; set; }
|
||||
[JsonPropertyName("enable_coalesce_read_write")]
|
||||
public Boolean EnableCoalesceReadWrite { get; set; }
|
||||
|
||||
[JsonPropertyName("enable_embedded_tracker")]
|
||||
public Boolean EnableEmbeddedTracker { get; set; }
|
||||
[JsonPropertyName("enable_embedded_tracker")]
|
||||
public Boolean EnableEmbeddedTracker { get; set; }
|
||||
|
||||
[JsonPropertyName("enable_multi_connections_from_same_ip")]
|
||||
public Boolean EnableMultiConnectionsFromSameIp { get; set; }
|
||||
[JsonPropertyName("enable_multi_connections_from_same_ip")]
|
||||
public Boolean EnableMultiConnectionsFromSameIp { get; set; }
|
||||
|
||||
[JsonPropertyName("enable_os_cache")]
|
||||
public Boolean EnableOsCache { get; set; }
|
||||
[JsonPropertyName("enable_os_cache")]
|
||||
public Boolean EnableOsCache { get; set; }
|
||||
|
||||
[JsonPropertyName("enable_piece_extent_affinity")]
|
||||
public Boolean EnablePieceExtentAffinity { get; set; }
|
||||
[JsonPropertyName("enable_piece_extent_affinity")]
|
||||
public Boolean EnablePieceExtentAffinity { get; set; }
|
||||
|
||||
[JsonPropertyName("enable_super_seeding")]
|
||||
public Boolean EnableSuperSeeding { get; set; }
|
||||
[JsonPropertyName("enable_super_seeding")]
|
||||
public Boolean EnableSuperSeeding { get; set; }
|
||||
|
||||
[JsonPropertyName("enable_upload_suggestions")]
|
||||
public Boolean EnableUploadSuggestions { get; set; }
|
||||
[JsonPropertyName("enable_upload_suggestions")]
|
||||
public Boolean EnableUploadSuggestions { get; set; }
|
||||
|
||||
[JsonPropertyName("encryption")]
|
||||
public Int64 Encryption { get; set; }
|
||||
[JsonPropertyName("encryption")]
|
||||
public Int64 Encryption { get; set; }
|
||||
|
||||
[JsonPropertyName("export_dir")]
|
||||
public String ExportDir { get; set; }
|
||||
[JsonPropertyName("export_dir")]
|
||||
public String ExportDir { get; set; }
|
||||
|
||||
[JsonPropertyName("export_dir_fin")]
|
||||
public String ExportDirFin { get; set; }
|
||||
[JsonPropertyName("export_dir_fin")]
|
||||
public String ExportDirFin { get; set; }
|
||||
|
||||
[JsonPropertyName("file_pool_size")]
|
||||
public Int64 FilePoolSize { get; set; }
|
||||
[JsonPropertyName("file_pool_size")]
|
||||
public Int64 FilePoolSize { get; set; }
|
||||
|
||||
[JsonPropertyName("incomplete_files_ext")]
|
||||
public Boolean IncompleteFilesExt { get; set; }
|
||||
[JsonPropertyName("incomplete_files_ext")]
|
||||
public Boolean IncompleteFilesExt { get; set; }
|
||||
|
||||
[JsonPropertyName("ip_filter_enabled")]
|
||||
public Boolean IpFilterEnabled { get; set; }
|
||||
[JsonPropertyName("ip_filter_enabled")]
|
||||
public Boolean IpFilterEnabled { get; set; }
|
||||
|
||||
[JsonPropertyName("ip_filter_path")]
|
||||
public String IpFilterPath { get; set; }
|
||||
[JsonPropertyName("ip_filter_path")]
|
||||
public String IpFilterPath { get; set; }
|
||||
|
||||
[JsonPropertyName("ip_filter_trackers")]
|
||||
public Boolean IpFilterTrackers { get; set; }
|
||||
[JsonPropertyName("ip_filter_trackers")]
|
||||
public Boolean IpFilterTrackers { get; set; }
|
||||
|
||||
[JsonPropertyName("limit_lan_peers")]
|
||||
public Boolean LimitLanPeers { get; set; }
|
||||
[JsonPropertyName("limit_lan_peers")]
|
||||
public Boolean LimitLanPeers { get; set; }
|
||||
|
||||
[JsonPropertyName("limit_tcp_overhead")]
|
||||
public Boolean LimitTcpOverhead { get; set; }
|
||||
[JsonPropertyName("limit_tcp_overhead")]
|
||||
public Boolean LimitTcpOverhead { get; set; }
|
||||
|
||||
[JsonPropertyName("limit_utp_rate")]
|
||||
public Boolean LimitUtpRate { get; set; }
|
||||
[JsonPropertyName("limit_utp_rate")]
|
||||
public Boolean LimitUtpRate { get; set; }
|
||||
|
||||
[JsonPropertyName("listen_port")]
|
||||
public Int64 ListenPort { get; set; }
|
||||
[JsonPropertyName("listen_port")]
|
||||
public Int64 ListenPort { get; set; }
|
||||
|
||||
[JsonPropertyName("locale")]
|
||||
public String Locale { get; set; }
|
||||
[JsonPropertyName("locale")]
|
||||
public String Locale { get; set; }
|
||||
|
||||
[JsonPropertyName("lsd")]
|
||||
public Boolean Lsd { get; set; }
|
||||
[JsonPropertyName("lsd")]
|
||||
public Boolean Lsd { get; set; }
|
||||
|
||||
[JsonPropertyName("mail_notification_auth_enabled")]
|
||||
public Boolean MailNotificationAuthEnabled { get; set; }
|
||||
[JsonPropertyName("mail_notification_auth_enabled")]
|
||||
public Boolean MailNotificationAuthEnabled { get; set; }
|
||||
|
||||
[JsonPropertyName("mail_notification_email")]
|
||||
public String MailNotificationEmail { get; set; }
|
||||
[JsonPropertyName("mail_notification_email")]
|
||||
public String MailNotificationEmail { get; set; }
|
||||
|
||||
[JsonPropertyName("mail_notification_enabled")]
|
||||
public Boolean MailNotificationEnabled { get; set; }
|
||||
[JsonPropertyName("mail_notification_enabled")]
|
||||
public Boolean MailNotificationEnabled { get; set; }
|
||||
|
||||
[JsonPropertyName("mail_notification_password")]
|
||||
public String MailNotificationPassword { get; set; }
|
||||
[JsonPropertyName("mail_notification_password")]
|
||||
public String MailNotificationPassword { get; set; }
|
||||
|
||||
[JsonPropertyName("mail_notification_sender")]
|
||||
public String MailNotificationSender { get; set; }
|
||||
[JsonPropertyName("mail_notification_sender")]
|
||||
public String MailNotificationSender { get; set; }
|
||||
|
||||
[JsonPropertyName("mail_notification_smtp")]
|
||||
public String MailNotificationSmtp { get; set; }
|
||||
[JsonPropertyName("mail_notification_smtp")]
|
||||
public String MailNotificationSmtp { get; set; }
|
||||
|
||||
[JsonPropertyName("mail_notification_ssl_enabled")]
|
||||
public Boolean MailNotificationSslEnabled { get; set; }
|
||||
[JsonPropertyName("mail_notification_ssl_enabled")]
|
||||
public Boolean MailNotificationSslEnabled { get; set; }
|
||||
|
||||
[JsonPropertyName("mail_notification_username")]
|
||||
public String MailNotificationUsername { get; set; }
|
||||
[JsonPropertyName("mail_notification_username")]
|
||||
public String MailNotificationUsername { get; set; }
|
||||
|
||||
[JsonPropertyName("max_active_downloads")]
|
||||
public Int64 MaxActiveDownloads { get; set; }
|
||||
[JsonPropertyName("max_active_downloads")]
|
||||
public Int64 MaxActiveDownloads { get; set; }
|
||||
|
||||
[JsonPropertyName("max_active_torrents")]
|
||||
public Int64 MaxActiveTorrents { get; set; }
|
||||
[JsonPropertyName("max_active_torrents")]
|
||||
public Int64 MaxActiveTorrents { get; set; }
|
||||
|
||||
[JsonPropertyName("max_active_uploads")]
|
||||
public Int64 MaxActiveUploads { get; set; }
|
||||
[JsonPropertyName("max_active_uploads")]
|
||||
public Int64 MaxActiveUploads { get; set; }
|
||||
|
||||
[JsonPropertyName("max_connec")]
|
||||
public Int64 MaxConnec { get; set; }
|
||||
[JsonPropertyName("max_connec")]
|
||||
public Int64 MaxConnec { get; set; }
|
||||
|
||||
[JsonPropertyName("max_connec_per_torrent")]
|
||||
public Int64 MaxConnecPerTorrent { get; set; }
|
||||
[JsonPropertyName("max_connec_per_torrent")]
|
||||
public Int64 MaxConnecPerTorrent { get; set; }
|
||||
|
||||
[JsonPropertyName("max_ratio")]
|
||||
public Int64 MaxRatio { get; set; }
|
||||
[JsonPropertyName("max_ratio")]
|
||||
public Int64 MaxRatio { get; set; }
|
||||
|
||||
[JsonPropertyName("max_ratio_act")]
|
||||
public Int64 MaxRatioAct { get; set; }
|
||||
[JsonPropertyName("max_ratio_act")]
|
||||
public Int64 MaxRatioAct { get; set; }
|
||||
|
||||
[JsonPropertyName("max_ratio_enabled")]
|
||||
public Boolean MaxRatioEnabled { get; set; }
|
||||
[JsonPropertyName("max_ratio_enabled")]
|
||||
public Boolean MaxRatioEnabled { get; set; }
|
||||
|
||||
[JsonPropertyName("max_seeding_time")]
|
||||
public Int64 MaxSeedingTime { get; set; }
|
||||
[JsonPropertyName("max_seeding_time")]
|
||||
public Int64 MaxSeedingTime { get; set; }
|
||||
|
||||
[JsonPropertyName("max_seeding_time_enabled")]
|
||||
public Boolean MaxSeedingTimeEnabled { get; set; }
|
||||
[JsonPropertyName("max_seeding_time_enabled")]
|
||||
public Boolean MaxSeedingTimeEnabled { get; set; }
|
||||
|
||||
[JsonPropertyName("max_uploads")]
|
||||
public Int64 MaxUploads { get; set; }
|
||||
[JsonPropertyName("max_uploads")]
|
||||
public Int64 MaxUploads { get; set; }
|
||||
|
||||
[JsonPropertyName("max_uploads_per_torrent")]
|
||||
public Int64 MaxUploadsPerTorrent { get; set; }
|
||||
[JsonPropertyName("max_uploads_per_torrent")]
|
||||
public Int64 MaxUploadsPerTorrent { get; set; }
|
||||
|
||||
[JsonPropertyName("outgoing_ports_max")]
|
||||
public Int64 OutgoingPortsMax { get; set; }
|
||||
[JsonPropertyName("outgoing_ports_max")]
|
||||
public Int64 OutgoingPortsMax { get; set; }
|
||||
|
||||
[JsonPropertyName("outgoing_ports_min")]
|
||||
public Int64 OutgoingPortsMin { get; set; }
|
||||
[JsonPropertyName("outgoing_ports_min")]
|
||||
public Int64 OutgoingPortsMin { get; set; }
|
||||
|
||||
[JsonPropertyName("pex")]
|
||||
public Boolean Pex { get; set; }
|
||||
[JsonPropertyName("pex")]
|
||||
public Boolean Pex { get; set; }
|
||||
|
||||
[JsonPropertyName("preallocate_all")]
|
||||
public Boolean PreallocateAll { get; set; }
|
||||
[JsonPropertyName("preallocate_all")]
|
||||
public Boolean PreallocateAll { get; set; }
|
||||
|
||||
[JsonPropertyName("proxy_auth_enabled")]
|
||||
public Boolean ProxyAuthEnabled { get; set; }
|
||||
[JsonPropertyName("proxy_auth_enabled")]
|
||||
public Boolean ProxyAuthEnabled { get; set; }
|
||||
|
||||
[JsonPropertyName("proxy_ip")]
|
||||
public String ProxyIp { get; set; }
|
||||
[JsonPropertyName("proxy_ip")]
|
||||
public String ProxyIp { get; set; }
|
||||
|
||||
[JsonPropertyName("proxy_password")]
|
||||
public String ProxyPassword { get; set; }
|
||||
[JsonPropertyName("proxy_password")]
|
||||
public String ProxyPassword { get; set; }
|
||||
|
||||
[JsonPropertyName("proxy_peer_connections")]
|
||||
public Boolean ProxyPeerConnections { get; set; }
|
||||
[JsonPropertyName("proxy_peer_connections")]
|
||||
public Boolean ProxyPeerConnections { get; set; }
|
||||
|
||||
[JsonPropertyName("proxy_port")]
|
||||
public Int64 ProxyPort { get; set; }
|
||||
[JsonPropertyName("proxy_port")]
|
||||
public Int64 ProxyPort { get; set; }
|
||||
|
||||
[JsonPropertyName("proxy_torrents_only")]
|
||||
public Boolean ProxyTorrentsOnly { get; set; }
|
||||
[JsonPropertyName("proxy_torrents_only")]
|
||||
public Boolean ProxyTorrentsOnly { get; set; }
|
||||
|
||||
[JsonPropertyName("proxy_type")]
|
||||
public Int64 ProxyType { get; set; }
|
||||
[JsonPropertyName("proxy_type")]
|
||||
public Int64 ProxyType { get; set; }
|
||||
|
||||
[JsonPropertyName("proxy_username")]
|
||||
public String ProxyUsername { get; set; }
|
||||
[JsonPropertyName("proxy_username")]
|
||||
public String ProxyUsername { get; set; }
|
||||
|
||||
[JsonPropertyName("queueing_enabled")]
|
||||
public Boolean QueueingEnabled { get; set; }
|
||||
[JsonPropertyName("queueing_enabled")]
|
||||
public Boolean QueueingEnabled { get; set; }
|
||||
|
||||
[JsonPropertyName("random_port")]
|
||||
public Boolean RandomPort { get; set; }
|
||||
[JsonPropertyName("random_port")]
|
||||
public Boolean RandomPort { get; set; }
|
||||
|
||||
[JsonPropertyName("recheck_completed_torrents")]
|
||||
public Boolean RecheckCompletedTorrents { get; set; }
|
||||
[JsonPropertyName("recheck_completed_torrents")]
|
||||
public Boolean RecheckCompletedTorrents { get; set; }
|
||||
|
||||
[JsonPropertyName("resolve_peer_countries")]
|
||||
public Boolean ResolvePeerCountries { get; set; }
|
||||
[JsonPropertyName("resolve_peer_countries")]
|
||||
public Boolean ResolvePeerCountries { get; set; }
|
||||
|
||||
[JsonPropertyName("rss_auto_downloading_enabled")]
|
||||
public Boolean RssAutoDownloadingEnabled { get; set; }
|
||||
[JsonPropertyName("rss_auto_downloading_enabled")]
|
||||
public Boolean RssAutoDownloadingEnabled { get; set; }
|
||||
|
||||
[JsonPropertyName("rss_max_articles_per_feed")]
|
||||
public Int64 RssMaxArticlesPerFeed { get; set; }
|
||||
[JsonPropertyName("rss_max_articles_per_feed")]
|
||||
public Int64 RssMaxArticlesPerFeed { get; set; }
|
||||
|
||||
[JsonPropertyName("rss_processing_enabled")]
|
||||
public Boolean RssProcessingEnabled { get; set; }
|
||||
[JsonPropertyName("rss_processing_enabled")]
|
||||
public Boolean RssProcessingEnabled { get; set; }
|
||||
|
||||
[JsonPropertyName("rss_refresh_interval")]
|
||||
public Int64 RssRefreshInterval { get; set; }
|
||||
[JsonPropertyName("rss_refresh_interval")]
|
||||
public Int64 RssRefreshInterval { get; set; }
|
||||
|
||||
[JsonPropertyName("save_path")]
|
||||
public String SavePath { get; set; }
|
||||
[JsonPropertyName("save_path")]
|
||||
public String SavePath { get; set; }
|
||||
|
||||
[JsonPropertyName("save_path_changed_tmm_enabled")]
|
||||
public Boolean SavePathChangedTmmEnabled { get; set; }
|
||||
[JsonPropertyName("save_path_changed_tmm_enabled")]
|
||||
public Boolean SavePathChangedTmmEnabled { get; set; }
|
||||
|
||||
[JsonPropertyName("save_resume_data_interval")]
|
||||
public Int64 SaveResumeDataInterval { get; set; }
|
||||
[JsonPropertyName("save_resume_data_interval")]
|
||||
public Int64 SaveResumeDataInterval { get; set; }
|
||||
|
||||
[JsonPropertyName("scan_dirs")]
|
||||
public ScanDirs ScanDirs { get; set; }
|
||||
[JsonPropertyName("scan_dirs")]
|
||||
public ScanDirs ScanDirs { get; set; }
|
||||
|
||||
[JsonPropertyName("schedule_from_hour")]
|
||||
public Int64 ScheduleFromHour { get; set; }
|
||||
[JsonPropertyName("schedule_from_hour")]
|
||||
public Int64 ScheduleFromHour { get; set; }
|
||||
|
||||
[JsonPropertyName("schedule_from_min")]
|
||||
public Int64 ScheduleFromMin { get; set; }
|
||||
[JsonPropertyName("schedule_from_min")]
|
||||
public Int64 ScheduleFromMin { get; set; }
|
||||
|
||||
[JsonPropertyName("schedule_to_hour")]
|
||||
public Int64 ScheduleToHour { get; set; }
|
||||
[JsonPropertyName("schedule_to_hour")]
|
||||
public Int64 ScheduleToHour { get; set; }
|
||||
|
||||
[JsonPropertyName("schedule_to_min")]
|
||||
public Int64 ScheduleToMin { get; set; }
|
||||
[JsonPropertyName("schedule_to_min")]
|
||||
public Int64 ScheduleToMin { get; set; }
|
||||
|
||||
[JsonPropertyName("scheduler_days")]
|
||||
public Int64 SchedulerDays { get; set; }
|
||||
[JsonPropertyName("scheduler_days")]
|
||||
public Int64 SchedulerDays { get; set; }
|
||||
|
||||
[JsonPropertyName("scheduler_enabled")]
|
||||
public Boolean SchedulerEnabled { get; set; }
|
||||
[JsonPropertyName("scheduler_enabled")]
|
||||
public Boolean SchedulerEnabled { get; set; }
|
||||
|
||||
[JsonPropertyName("send_buffer_low_watermark")]
|
||||
public Int64 SendBufferLowWatermark { get; set; }
|
||||
[JsonPropertyName("send_buffer_low_watermark")]
|
||||
public Int64 SendBufferLowWatermark { get; set; }
|
||||
|
||||
[JsonPropertyName("send_buffer_watermark")]
|
||||
public Int64 SendBufferWatermark { get; set; }
|
||||
[JsonPropertyName("send_buffer_watermark")]
|
||||
public Int64 SendBufferWatermark { get; set; }
|
||||
|
||||
[JsonPropertyName("send_buffer_watermark_factor")]
|
||||
public Int64 SendBufferWatermarkFactor { get; set; }
|
||||
[JsonPropertyName("send_buffer_watermark_factor")]
|
||||
public Int64 SendBufferWatermarkFactor { get; set; }
|
||||
|
||||
[JsonPropertyName("slow_torrent_dl_rate_threshold")]
|
||||
public Int64 SlowTorrentDlRateThreshold { get; set; }
|
||||
[JsonPropertyName("slow_torrent_dl_rate_threshold")]
|
||||
public Int64 SlowTorrentDlRateThreshold { get; set; }
|
||||
|
||||
[JsonPropertyName("slow_torrent_inactive_timer")]
|
||||
public Int64 SlowTorrentInactiveTimer { get; set; }
|
||||
[JsonPropertyName("slow_torrent_inactive_timer")]
|
||||
public Int64 SlowTorrentInactiveTimer { get; set; }
|
||||
|
||||
[JsonPropertyName("slow_torrent_ul_rate_threshold")]
|
||||
public Int64 SlowTorrentUlRateThreshold { get; set; }
|
||||
[JsonPropertyName("slow_torrent_ul_rate_threshold")]
|
||||
public Int64 SlowTorrentUlRateThreshold { get; set; }
|
||||
|
||||
[JsonPropertyName("socket_backlog_size")]
|
||||
public Int64 SocketBacklogSize { get; set; }
|
||||
[JsonPropertyName("socket_backlog_size")]
|
||||
public Int64 SocketBacklogSize { get; set; }
|
||||
|
||||
[JsonPropertyName("start_paused_enabled")]
|
||||
public Boolean StartPausedEnabled { get; set; }
|
||||
[JsonPropertyName("start_paused_enabled")]
|
||||
public Boolean StartPausedEnabled { get; set; }
|
||||
|
||||
[JsonPropertyName("stop_tracker_timeout")]
|
||||
public Int64 StopTrackerTimeout { get; set; }
|
||||
[JsonPropertyName("stop_tracker_timeout")]
|
||||
public Int64 StopTrackerTimeout { get; set; }
|
||||
|
||||
[JsonPropertyName("temp_path")]
|
||||
public String TempPath { get; set; }
|
||||
[JsonPropertyName("temp_path")]
|
||||
public String TempPath { get; set; }
|
||||
|
||||
[JsonPropertyName("temp_path_enabled")]
|
||||
public Boolean TempPathEnabled { get; set; }
|
||||
[JsonPropertyName("temp_path_enabled")]
|
||||
public Boolean TempPathEnabled { get; set; }
|
||||
|
||||
[JsonPropertyName("torrent_changed_tmm_enabled")]
|
||||
public Boolean TorrentChangedTmmEnabled { get; set; }
|
||||
[JsonPropertyName("torrent_changed_tmm_enabled")]
|
||||
public Boolean TorrentChangedTmmEnabled { get; set; }
|
||||
|
||||
[JsonPropertyName("up_limit")]
|
||||
public Int64 UpLimit { get; set; }
|
||||
[JsonPropertyName("up_limit")]
|
||||
public Int64 UpLimit { get; set; }
|
||||
|
||||
[JsonPropertyName("upload_choking_algorithm")]
|
||||
public Int64 UploadChokingAlgorithm { get; set; }
|
||||
[JsonPropertyName("upload_choking_algorithm")]
|
||||
public Int64 UploadChokingAlgorithm { get; set; }
|
||||
|
||||
[JsonPropertyName("upload_slots_behavior")]
|
||||
public Int64 UploadSlotsBehavior { get; set; }
|
||||
[JsonPropertyName("upload_slots_behavior")]
|
||||
public Int64 UploadSlotsBehavior { get; set; }
|
||||
|
||||
[JsonPropertyName("upnp")]
|
||||
public Boolean Upnp { get; set; }
|
||||
[JsonPropertyName("upnp")]
|
||||
public Boolean Upnp { get; set; }
|
||||
|
||||
[JsonPropertyName("upnp_lease_duration")]
|
||||
public Int64 UpnpLeaseDuration { get; set; }
|
||||
[JsonPropertyName("upnp_lease_duration")]
|
||||
public Int64 UpnpLeaseDuration { get; set; }
|
||||
|
||||
[JsonPropertyName("use_https")]
|
||||
public Boolean UseHttps { get; set; }
|
||||
[JsonPropertyName("use_https")]
|
||||
public Boolean UseHttps { get; set; }
|
||||
|
||||
[JsonPropertyName("utp_tcp_mixed_mode")]
|
||||
public Int64 UtpTcpMixedMode { get; set; }
|
||||
[JsonPropertyName("utp_tcp_mixed_mode")]
|
||||
public Int64 UtpTcpMixedMode { get; set; }
|
||||
|
||||
[JsonPropertyName("web_ui_address")]
|
||||
public String WebUiAddress { get; set; }
|
||||
[JsonPropertyName("web_ui_address")]
|
||||
public String WebUiAddress { get; set; }
|
||||
|
||||
[JsonPropertyName("web_ui_ban_duration")]
|
||||
public Int64 WebUiBanDuration { get; set; }
|
||||
[JsonPropertyName("web_ui_ban_duration")]
|
||||
public Int64 WebUiBanDuration { get; set; }
|
||||
|
||||
[JsonPropertyName("web_ui_clickjacking_protection_enabled")]
|
||||
public Boolean WebUiClickjackingProtectionEnabled { get; set; }
|
||||
[JsonPropertyName("web_ui_clickjacking_protection_enabled")]
|
||||
public Boolean WebUiClickjackingProtectionEnabled { get; set; }
|
||||
|
||||
[JsonPropertyName("web_ui_csrf_protection_enabled")]
|
||||
public Boolean WebUiCsrfProtectionEnabled { get; set; }
|
||||
[JsonPropertyName("web_ui_csrf_protection_enabled")]
|
||||
public Boolean WebUiCsrfProtectionEnabled { get; set; }
|
||||
|
||||
[JsonPropertyName("web_ui_domain_list")]
|
||||
public String WebUiDomainList { get; set; }
|
||||
[JsonPropertyName("web_ui_domain_list")]
|
||||
public String WebUiDomainList { get; set; }
|
||||
|
||||
[JsonPropertyName("web_ui_host_header_validation_enabled")]
|
||||
public Boolean WebUiHostHeaderValidationEnabled { get; set; }
|
||||
[JsonPropertyName("web_ui_host_header_validation_enabled")]
|
||||
public Boolean WebUiHostHeaderValidationEnabled { get; set; }
|
||||
|
||||
[JsonPropertyName("web_ui_https_cert_path")]
|
||||
public String WebUiHttpsCertPath { get; set; }
|
||||
[JsonPropertyName("web_ui_https_cert_path")]
|
||||
public String WebUiHttpsCertPath { get; set; }
|
||||
|
||||
[JsonPropertyName("web_ui_https_key_path")]
|
||||
public String WebUiHttpsKeyPath { get; set; }
|
||||
[JsonPropertyName("web_ui_https_key_path")]
|
||||
public String WebUiHttpsKeyPath { get; set; }
|
||||
|
||||
[JsonPropertyName("web_ui_max_auth_fail_count")]
|
||||
public Int64 WebUiMaxAuthFailCount { get; set; }
|
||||
[JsonPropertyName("web_ui_max_auth_fail_count")]
|
||||
public Int64 WebUiMaxAuthFailCount { get; set; }
|
||||
|
||||
[JsonPropertyName("web_ui_port")]
|
||||
public Int64 WebUiPort { get; set; }
|
||||
[JsonPropertyName("web_ui_port")]
|
||||
public Int64 WebUiPort { get; set; }
|
||||
|
||||
[JsonPropertyName("web_ui_secure_cookie_enabled")]
|
||||
public Boolean WebUiSecureCookieEnabled { get; set; }
|
||||
[JsonPropertyName("web_ui_secure_cookie_enabled")]
|
||||
public Boolean WebUiSecureCookieEnabled { get; set; }
|
||||
|
||||
[JsonPropertyName("web_ui_session_timeout")]
|
||||
public Int64 WebUiSessionTimeout { get; set; }
|
||||
[JsonPropertyName("web_ui_session_timeout")]
|
||||
public Int64 WebUiSessionTimeout { get; set; }
|
||||
|
||||
[JsonPropertyName("web_ui_upnp")]
|
||||
public Boolean WebUiUpnp { get; set; }
|
||||
[JsonPropertyName("web_ui_upnp")]
|
||||
public Boolean WebUiUpnp { get; set; }
|
||||
|
||||
[JsonPropertyName("web_ui_username")]
|
||||
public String WebUiUsername { get; set; }
|
||||
}
|
||||
[JsonPropertyName("web_ui_username")]
|
||||
public String WebUiUsername { get; set; }
|
||||
}
|
||||
|
||||
public class ScanDirs
|
||||
{
|
||||
}
|
||||
}
|
||||
public class ScanDirs
|
||||
{
|
||||
}
|
||||
|
|
@ -1,106 +1,102 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using RdtClient.Data.Models.QBittorrent.QuickType;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace RdtClient.Data.Models.QBittorrent
|
||||
namespace RdtClient.Data.Models.QBittorrent;
|
||||
|
||||
public class SyncMetaData
|
||||
{
|
||||
public class SyncMetaData
|
||||
{
|
||||
[JsonPropertyName("categories")]
|
||||
public IDictionary<String, TorrentCategory> Categories { get; set; }
|
||||
[JsonPropertyName("categories")]
|
||||
public IDictionary<String, TorrentCategory> Categories { get; set; }
|
||||
|
||||
[JsonPropertyName("full_update")]
|
||||
public Boolean FullUpdate { get; set; }
|
||||
[JsonPropertyName("full_update")]
|
||||
public Boolean FullUpdate { get; set; }
|
||||
|
||||
[JsonPropertyName("rid")]
|
||||
public Int64 Rid { get; set; }
|
||||
[JsonPropertyName("rid")]
|
||||
public Int64 Rid { get; set; }
|
||||
|
||||
[JsonPropertyName("server_state")]
|
||||
public SyncMetaDataServerState ServerState { get; set; }
|
||||
[JsonPropertyName("server_state")]
|
||||
public SyncMetaDataServerState ServerState { get; set; }
|
||||
|
||||
[JsonPropertyName("tags")]
|
||||
public IList<Object> Tags { get; set; }
|
||||
[JsonPropertyName("tags")]
|
||||
public IList<Object> Tags { get; set; }
|
||||
|
||||
[JsonPropertyName("torrents")]
|
||||
public IDictionary<String, TorrentInfo> Torrents { get; set; }
|
||||
[JsonPropertyName("torrents")]
|
||||
public IDictionary<String, TorrentInfo> Torrents { get; set; }
|
||||
|
||||
[JsonPropertyName("trackers")]
|
||||
public IDictionary<String, List<String>> Trackers { get; set; }
|
||||
}
|
||||
|
||||
public class SyncMetaDataServerState
|
||||
{
|
||||
[JsonPropertyName("alltime_dl")]
|
||||
public Int64 AlltimeDl { get; set; }
|
||||
|
||||
[JsonPropertyName("alltime_ul")]
|
||||
public Int64 AlltimeUl { get; set; }
|
||||
|
||||
[JsonPropertyName("average_time_queue")]
|
||||
public Int64 AverageTimeQueue { get; set; }
|
||||
|
||||
[JsonPropertyName("connection_status")]
|
||||
public String ConnectionStatus { get; set; }
|
||||
|
||||
[JsonPropertyName("dht_nodes")]
|
||||
public Int64 DhtNodes { get; set; }
|
||||
|
||||
[JsonPropertyName("dl_info_data")]
|
||||
public Int64 DlInfoData { get; set; }
|
||||
|
||||
[JsonPropertyName("dl_info_speed")]
|
||||
public Int64 DlInfoSpeed { get; set; }
|
||||
|
||||
[JsonPropertyName("dl_rate_limit")]
|
||||
public Int64 DlRateLimit { get; set; }
|
||||
|
||||
[JsonPropertyName("free_space_on_disk")]
|
||||
public Int64 FreeSpaceOnDisk { get; set; }
|
||||
|
||||
[JsonPropertyName("global_ratio")]
|
||||
public String GlobalRatio { get; set; }
|
||||
|
||||
[JsonPropertyName("queued_io_jobs")]
|
||||
public Int64 QueuedIoJobs { get; set; }
|
||||
|
||||
[JsonPropertyName("queueing")]
|
||||
public Boolean Queueing { get; set; }
|
||||
|
||||
[JsonPropertyName("read_cache_hits")]
|
||||
public String ReadCacheHits { get; set; }
|
||||
|
||||
[JsonPropertyName("read_cache_overload")]
|
||||
public String ReadCacheOverload { get; set; }
|
||||
|
||||
[JsonPropertyName("refresh_interval")]
|
||||
public Int64 RefreshInterval { get; set; }
|
||||
|
||||
[JsonPropertyName("total_buffers_size")]
|
||||
public Int64 TotalBuffersSize { get; set; }
|
||||
|
||||
[JsonPropertyName("total_peer_connections")]
|
||||
public Int64 TotalPeerConnections { get; set; }
|
||||
|
||||
[JsonPropertyName("total_queued_size")]
|
||||
public Int64 TotalQueuedSize { get; set; }
|
||||
|
||||
[JsonPropertyName("total_wasted_session")]
|
||||
public Int64 TotalWastedSession { get; set; }
|
||||
|
||||
[JsonPropertyName("up_info_data")]
|
||||
public Int64 UpInfoData { get; set; }
|
||||
|
||||
[JsonPropertyName("up_info_speed")]
|
||||
public Int64 UpInfoSpeed { get; set; }
|
||||
|
||||
[JsonPropertyName("up_rate_limit")]
|
||||
public Int64 UpRateLimit { get; set; }
|
||||
|
||||
[JsonPropertyName("use_alt_speed_limits")]
|
||||
public Boolean UseAltSpeedLimits { get; set; }
|
||||
|
||||
[JsonPropertyName("write_cache_overload")]
|
||||
public String WriteCacheOverload { get; set; }
|
||||
}
|
||||
[JsonPropertyName("trackers")]
|
||||
public IDictionary<String, List<String>> Trackers { get; set; }
|
||||
}
|
||||
|
||||
public class SyncMetaDataServerState
|
||||
{
|
||||
[JsonPropertyName("alltime_dl")]
|
||||
public Int64 AlltimeDl { get; set; }
|
||||
|
||||
[JsonPropertyName("alltime_ul")]
|
||||
public Int64 AlltimeUl { get; set; }
|
||||
|
||||
[JsonPropertyName("average_time_queue")]
|
||||
public Int64 AverageTimeQueue { get; set; }
|
||||
|
||||
[JsonPropertyName("connection_status")]
|
||||
public String ConnectionStatus { get; set; }
|
||||
|
||||
[JsonPropertyName("dht_nodes")]
|
||||
public Int64 DhtNodes { get; set; }
|
||||
|
||||
[JsonPropertyName("dl_info_data")]
|
||||
public Int64 DlInfoData { get; set; }
|
||||
|
||||
[JsonPropertyName("dl_info_speed")]
|
||||
public Int64 DlInfoSpeed { get; set; }
|
||||
|
||||
[JsonPropertyName("dl_rate_limit")]
|
||||
public Int64 DlRateLimit { get; set; }
|
||||
|
||||
[JsonPropertyName("free_space_on_disk")]
|
||||
public Int64 FreeSpaceOnDisk { get; set; }
|
||||
|
||||
[JsonPropertyName("global_ratio")]
|
||||
public String GlobalRatio { get; set; }
|
||||
|
||||
[JsonPropertyName("queued_io_jobs")]
|
||||
public Int64 QueuedIoJobs { get; set; }
|
||||
|
||||
[JsonPropertyName("queueing")]
|
||||
public Boolean Queueing { get; set; }
|
||||
|
||||
[JsonPropertyName("read_cache_hits")]
|
||||
public String ReadCacheHits { get; set; }
|
||||
|
||||
[JsonPropertyName("read_cache_overload")]
|
||||
public String ReadCacheOverload { get; set; }
|
||||
|
||||
[JsonPropertyName("refresh_interval")]
|
||||
public Int64 RefreshInterval { get; set; }
|
||||
|
||||
[JsonPropertyName("total_buffers_size")]
|
||||
public Int64 TotalBuffersSize { get; set; }
|
||||
|
||||
[JsonPropertyName("total_peer_connections")]
|
||||
public Int64 TotalPeerConnections { get; set; }
|
||||
|
||||
[JsonPropertyName("total_queued_size")]
|
||||
public Int64 TotalQueuedSize { get; set; }
|
||||
|
||||
[JsonPropertyName("total_wasted_session")]
|
||||
public Int64 TotalWastedSession { get; set; }
|
||||
|
||||
[JsonPropertyName("up_info_data")]
|
||||
public Int64 UpInfoData { get; set; }
|
||||
|
||||
[JsonPropertyName("up_info_speed")]
|
||||
public Int64 UpInfoSpeed { get; set; }
|
||||
|
||||
[JsonPropertyName("up_rate_limit")]
|
||||
public Int64 UpRateLimit { get; set; }
|
||||
|
||||
[JsonPropertyName("use_alt_speed_limits")]
|
||||
public Boolean UseAltSpeedLimits { get; set; }
|
||||
|
||||
[JsonPropertyName("write_cache_overload")]
|
||||
public String WriteCacheOverload { get; set; }
|
||||
}
|
||||
|
|
@ -1,14 +1,12 @@
|
|||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace RdtClient.Data.Models.QBittorrent
|
||||
namespace RdtClient.Data.Models.QBittorrent;
|
||||
|
||||
public class TorrentCategory
|
||||
{
|
||||
public class TorrentCategory
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public String Name { get; set; }
|
||||
[JsonPropertyName("name")]
|
||||
public String Name { get; set; }
|
||||
|
||||
[JsonPropertyName("savePath")]
|
||||
public String SavePath { get; set; }
|
||||
}
|
||||
}
|
||||
[JsonPropertyName("savePath")]
|
||||
public String SavePath { get; set; }
|
||||
}
|
||||
|
|
@ -1,11 +1,9 @@
|
|||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace RdtClient.Data.Models.QBittorrent
|
||||
namespace RdtClient.Data.Models.QBittorrent;
|
||||
|
||||
public class TorrentFileItem
|
||||
{
|
||||
public class TorrentFileItem
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public String Name { get; set; }
|
||||
}
|
||||
}
|
||||
[JsonPropertyName("name")]
|
||||
public String Name { get; set; }
|
||||
}
|
||||
|
|
@ -1,143 +1,138 @@
|
|||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace RdtClient.Data.Models.QBittorrent
|
||||
namespace RdtClient.Data.Models.QBittorrent;
|
||||
|
||||
public class TorrentInfo
|
||||
{
|
||||
namespace QuickType
|
||||
{
|
||||
public class TorrentInfo
|
||||
{
|
||||
[JsonPropertyName("added_on")]
|
||||
public Int64 AddedOn { get; set; }
|
||||
[JsonPropertyName("added_on")]
|
||||
public Int64 AddedOn { get; set; }
|
||||
|
||||
[JsonPropertyName("amount_left")]
|
||||
public Int64 AmountLeft { get; set; }
|
||||
[JsonPropertyName("amount_left")]
|
||||
public Int64 AmountLeft { get; set; }
|
||||
|
||||
[JsonPropertyName("auto_tmm")]
|
||||
public Boolean AutoTmm { get; set; }
|
||||
[JsonPropertyName("auto_tmm")]
|
||||
public Boolean AutoTmm { get; set; }
|
||||
|
||||
[JsonPropertyName("availability")]
|
||||
public Decimal Availability { get; set; }
|
||||
[JsonPropertyName("availability")]
|
||||
public Decimal Availability { get; set; }
|
||||
|
||||
[JsonPropertyName("category")]
|
||||
public String Category { get; set; }
|
||||
[JsonPropertyName("category")]
|
||||
public String Category { get; set; }
|
||||
|
||||
[JsonPropertyName("completed")]
|
||||
public Int64 Completed { get; set; }
|
||||
[JsonPropertyName("completed")]
|
||||
public Int64 Completed { get; set; }
|
||||
|
||||
[JsonPropertyName("completion_on")]
|
||||
public Int64? CompletionOn { get; set; }
|
||||
[JsonPropertyName("completion_on")]
|
||||
public Int64? CompletionOn { get; set; }
|
||||
|
||||
[JsonPropertyName("content_path")]
|
||||
public String ContentPath { get; set; }
|
||||
[JsonPropertyName("content_path")]
|
||||
public String ContentPath { get; set; }
|
||||
|
||||
[JsonPropertyName("dl_limit")]
|
||||
public Int64 DlLimit { get; set; }
|
||||
[JsonPropertyName("dl_limit")]
|
||||
public Int64 DlLimit { get; set; }
|
||||
|
||||
[JsonPropertyName("dlspeed")]
|
||||
public Int64 Dlspeed { get; set; }
|
||||
[JsonPropertyName("dlspeed")]
|
||||
public Int64 Dlspeed { get; set; }
|
||||
|
||||
[JsonPropertyName("downloaded")]
|
||||
public Int64 Downloaded { get; set; }
|
||||
[JsonPropertyName("downloaded")]
|
||||
public Int64 Downloaded { get; set; }
|
||||
|
||||
[JsonPropertyName("downloaded_session")]
|
||||
public Int64 DownloadedSession { get; set; }
|
||||
[JsonPropertyName("downloaded_session")]
|
||||
public Int64 DownloadedSession { get; set; }
|
||||
|
||||
[JsonPropertyName("eta")]
|
||||
public Int64 Eta { get; set; }
|
||||
[JsonPropertyName("eta")]
|
||||
public Int64 Eta { get; set; }
|
||||
|
||||
[JsonPropertyName("f_l_piece_prio")]
|
||||
public Boolean FlPiecePrio { get; set; }
|
||||
[JsonPropertyName("f_l_piece_prio")]
|
||||
public Boolean FlPiecePrio { get; set; }
|
||||
|
||||
[JsonPropertyName("force_start")]
|
||||
public Boolean ForceStart { get; set; }
|
||||
[JsonPropertyName("force_start")]
|
||||
public Boolean ForceStart { get; set; }
|
||||
|
||||
[JsonPropertyName("hash")]
|
||||
public String Hash { get; set; }
|
||||
[JsonPropertyName("hash")]
|
||||
public String Hash { get; set; }
|
||||
|
||||
[JsonPropertyName("last_activity")]
|
||||
public Int64 LastActivity { get; set; }
|
||||
[JsonPropertyName("last_activity")]
|
||||
public Int64 LastActivity { get; set; }
|
||||
|
||||
[JsonPropertyName("magnet_uri")]
|
||||
public String MagnetUri { get; set; }
|
||||
[JsonPropertyName("magnet_uri")]
|
||||
public String MagnetUri { get; set; }
|
||||
|
||||
[JsonPropertyName("max_ratio")]
|
||||
public Int64 MaxRatio { get; set; }
|
||||
[JsonPropertyName("max_ratio")]
|
||||
public Int64 MaxRatio { get; set; }
|
||||
|
||||
[JsonPropertyName("max_seeding_time")]
|
||||
public Int64 MaxSeedingTime { get; set; }
|
||||
[JsonPropertyName("max_seeding_time")]
|
||||
public Int64 MaxSeedingTime { get; set; }
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public String Name { get; set; }
|
||||
[JsonPropertyName("name")]
|
||||
public String Name { get; set; }
|
||||
|
||||
[JsonPropertyName("num_complete")]
|
||||
public Int64 NumComplete { get; set; }
|
||||
[JsonPropertyName("num_complete")]
|
||||
public Int64 NumComplete { get; set; }
|
||||
|
||||
[JsonPropertyName("num_incomplete")]
|
||||
public Int64 NumIncomplete { get; set; }
|
||||
[JsonPropertyName("num_incomplete")]
|
||||
public Int64 NumIncomplete { get; set; }
|
||||
|
||||
[JsonPropertyName("num_leechs")]
|
||||
public Int64 NumLeechs { get; set; }
|
||||
[JsonPropertyName("num_leechs")]
|
||||
public Int64 NumLeechs { get; set; }
|
||||
|
||||
[JsonPropertyName("num_seeds")]
|
||||
public Int64 NumSeeds { get; set; }
|
||||
[JsonPropertyName("num_seeds")]
|
||||
public Int64 NumSeeds { get; set; }
|
||||
|
||||
[JsonPropertyName("priority")]
|
||||
public Int64 Priority { get; set; }
|
||||
[JsonPropertyName("priority")]
|
||||
public Int64 Priority { get; set; }
|
||||
|
||||
[JsonPropertyName("progress")]
|
||||
public Single Progress { get; set; }
|
||||
[JsonPropertyName("progress")]
|
||||
public Single Progress { get; set; }
|
||||
|
||||
[JsonPropertyName("ratio")]
|
||||
public Int64 Ratio { get; set; }
|
||||
[JsonPropertyName("ratio")]
|
||||
public Int64 Ratio { get; set; }
|
||||
|
||||
[JsonPropertyName("ratio_limit")]
|
||||
public Int64 RatioLimit { get; set; }
|
||||
[JsonPropertyName("ratio_limit")]
|
||||
public Int64 RatioLimit { get; set; }
|
||||
|
||||
[JsonPropertyName("save_path")]
|
||||
public String SavePath { get; set; }
|
||||
[JsonPropertyName("save_path")]
|
||||
public String SavePath { get; set; }
|
||||
|
||||
[JsonPropertyName("seeding_time_limit")]
|
||||
public Int64 SeedingTimeLimit { get; set; }
|
||||
[JsonPropertyName("seeding_time_limit")]
|
||||
public Int64 SeedingTimeLimit { get; set; }
|
||||
|
||||
[JsonPropertyName("seen_complete")]
|
||||
public Int64 SeenComplete { get; set; }
|
||||
[JsonPropertyName("seen_complete")]
|
||||
public Int64 SeenComplete { get; set; }
|
||||
|
||||
[JsonPropertyName("seq_dl")]
|
||||
public Boolean SeqDl { get; set; }
|
||||
[JsonPropertyName("seq_dl")]
|
||||
public Boolean SeqDl { get; set; }
|
||||
|
||||
[JsonPropertyName("size")]
|
||||
public Int64 Size { get; set; }
|
||||
[JsonPropertyName("size")]
|
||||
public Int64 Size { get; set; }
|
||||
|
||||
[JsonPropertyName("state")]
|
||||
public String State { get; set; }
|
||||
[JsonPropertyName("state")]
|
||||
public String State { get; set; }
|
||||
|
||||
[JsonPropertyName("super_seeding")]
|
||||
public Boolean SuperSeeding { get; set; }
|
||||
[JsonPropertyName("super_seeding")]
|
||||
public Boolean SuperSeeding { get; set; }
|
||||
|
||||
[JsonPropertyName("tags")]
|
||||
public String Tags { get; set; }
|
||||
[JsonPropertyName("tags")]
|
||||
public String Tags { get; set; }
|
||||
|
||||
[JsonPropertyName("time_active")]
|
||||
public Int64 TimeActive { get; set; }
|
||||
[JsonPropertyName("time_active")]
|
||||
public Int64 TimeActive { get; set; }
|
||||
|
||||
[JsonPropertyName("total_size")]
|
||||
public Int64 TotalSize { get; set; }
|
||||
[JsonPropertyName("total_size")]
|
||||
public Int64 TotalSize { get; set; }
|
||||
|
||||
[JsonPropertyName("tracker")]
|
||||
public String Tracker { get; set; }
|
||||
[JsonPropertyName("tracker")]
|
||||
public String Tracker { get; set; }
|
||||
|
||||
[JsonPropertyName("up_limit")]
|
||||
public Int64 UpLimit { get; set; }
|
||||
[JsonPropertyName("up_limit")]
|
||||
public Int64 UpLimit { get; set; }
|
||||
|
||||
[JsonPropertyName("uploaded")]
|
||||
public Int64 Uploaded { get; set; }
|
||||
[JsonPropertyName("uploaded")]
|
||||
public Int64 Uploaded { get; set; }
|
||||
|
||||
[JsonPropertyName("uploaded_session")]
|
||||
public Int64 UploadedSession { get; set; }
|
||||
[JsonPropertyName("uploaded_session")]
|
||||
public Int64 UploadedSession { get; set; }
|
||||
|
||||
[JsonPropertyName("upspeed")]
|
||||
public Int64 Upspeed { get; set; }
|
||||
}
|
||||
}
|
||||
[JsonPropertyName("upspeed")]
|
||||
public Int64 Upspeed { get; set; }
|
||||
}
|
||||
|
|
@ -1,107 +1,105 @@
|
|||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace RdtClient.Data.Models.QBittorrent
|
||||
namespace RdtClient.Data.Models.QBittorrent;
|
||||
|
||||
public class TorrentProperties
|
||||
{
|
||||
public class TorrentProperties
|
||||
{
|
||||
[JsonPropertyName("addition_date")]
|
||||
public Int64 AdditionDate { get; set; }
|
||||
[JsonPropertyName("addition_date")]
|
||||
public Int64 AdditionDate { get; set; }
|
||||
|
||||
[JsonPropertyName("comment")]
|
||||
public String Comment { get; set; }
|
||||
[JsonPropertyName("comment")]
|
||||
public String Comment { get; set; }
|
||||
|
||||
[JsonPropertyName("completion_date")]
|
||||
public Int64 CompletionDate { get; set; }
|
||||
[JsonPropertyName("completion_date")]
|
||||
public Int64 CompletionDate { get; set; }
|
||||
|
||||
[JsonPropertyName("created_by")]
|
||||
public String CreatedBy { get; set; }
|
||||
[JsonPropertyName("created_by")]
|
||||
public String CreatedBy { get; set; }
|
||||
|
||||
[JsonPropertyName("creation_date")]
|
||||
public Int64 CreationDate { get; set; }
|
||||
[JsonPropertyName("creation_date")]
|
||||
public Int64 CreationDate { get; set; }
|
||||
|
||||
[JsonPropertyName("dl_limit")]
|
||||
public Int64 DlLimit { get; set; }
|
||||
[JsonPropertyName("dl_limit")]
|
||||
public Int64 DlLimit { get; set; }
|
||||
|
||||
[JsonPropertyName("dl_speed")]
|
||||
public Int64 DlSpeed { get; set; }
|
||||
[JsonPropertyName("dl_speed")]
|
||||
public Int64 DlSpeed { get; set; }
|
||||
|
||||
[JsonPropertyName("dl_speed_avg")]
|
||||
public Int64 DlSpeedAvg { get; set; }
|
||||
[JsonPropertyName("dl_speed_avg")]
|
||||
public Int64 DlSpeedAvg { get; set; }
|
||||
|
||||
[JsonPropertyName("eta")]
|
||||
public Int64 Eta { get; set; }
|
||||
[JsonPropertyName("eta")]
|
||||
public Int64 Eta { get; set; }
|
||||
|
||||
[JsonPropertyName("last_seen")]
|
||||
public Int64 LastSeen { get; set; }
|
||||
[JsonPropertyName("last_seen")]
|
||||
public Int64 LastSeen { get; set; }
|
||||
|
||||
[JsonPropertyName("nb_connections")]
|
||||
public Int64 NbConnections { get; set; }
|
||||
[JsonPropertyName("nb_connections")]
|
||||
public Int64 NbConnections { get; set; }
|
||||
|
||||
[JsonPropertyName("nb_connections_limit")]
|
||||
public Int64 NbConnectionsLimit { get; set; }
|
||||
[JsonPropertyName("nb_connections_limit")]
|
||||
public Int64 NbConnectionsLimit { get; set; }
|
||||
|
||||
[JsonPropertyName("peers")]
|
||||
public Int64 Peers { get; set; }
|
||||
[JsonPropertyName("peers")]
|
||||
public Int64 Peers { get; set; }
|
||||
|
||||
[JsonPropertyName("peers_total")]
|
||||
public Int64 PeersTotal { get; set; }
|
||||
[JsonPropertyName("peers_total")]
|
||||
public Int64 PeersTotal { get; set; }
|
||||
|
||||
[JsonPropertyName("piece_size")]
|
||||
public Int64 PieceSize { get; set; }
|
||||
[JsonPropertyName("piece_size")]
|
||||
public Int64 PieceSize { get; set; }
|
||||
|
||||
[JsonPropertyName("pieces_have")]
|
||||
public Int64 PiecesHave { get; set; }
|
||||
[JsonPropertyName("pieces_have")]
|
||||
public Int64 PiecesHave { get; set; }
|
||||
|
||||
[JsonPropertyName("pieces_num")]
|
||||
public Int64 PiecesNum { get; set; }
|
||||
[JsonPropertyName("pieces_num")]
|
||||
public Int64 PiecesNum { get; set; }
|
||||
|
||||
[JsonPropertyName("reannounce")]
|
||||
public Int64 Reannounce { get; set; }
|
||||
[JsonPropertyName("reannounce")]
|
||||
public Int64 Reannounce { get; set; }
|
||||
|
||||
[JsonPropertyName("save_path")]
|
||||
public String SavePath { get; set; }
|
||||
[JsonPropertyName("save_path")]
|
||||
public String SavePath { get; set; }
|
||||
|
||||
[JsonPropertyName("seeding_time")]
|
||||
public Int64 SeedingTime { get; set; }
|
||||
[JsonPropertyName("seeding_time")]
|
||||
public Int64 SeedingTime { get; set; }
|
||||
|
||||
[JsonPropertyName("seeds")]
|
||||
public Int64 Seeds { get; set; }
|
||||
[JsonPropertyName("seeds")]
|
||||
public Int64 Seeds { get; set; }
|
||||
|
||||
[JsonPropertyName("seeds_total")]
|
||||
public Int64 SeedsTotal { get; set; }
|
||||
[JsonPropertyName("seeds_total")]
|
||||
public Int64 SeedsTotal { get; set; }
|
||||
|
||||
[JsonPropertyName("share_ratio")]
|
||||
public Int64 ShareRatio { get; set; }
|
||||
[JsonPropertyName("share_ratio")]
|
||||
public Int64 ShareRatio { get; set; }
|
||||
|
||||
[JsonPropertyName("time_elapsed")]
|
||||
public Int64 TimeElapsed { get; set; }
|
||||
[JsonPropertyName("time_elapsed")]
|
||||
public Int64 TimeElapsed { get; set; }
|
||||
|
||||
[JsonPropertyName("total_downloaded")]
|
||||
public Int64 TotalDownloaded { get; set; }
|
||||
[JsonPropertyName("total_downloaded")]
|
||||
public Int64 TotalDownloaded { get; set; }
|
||||
|
||||
[JsonPropertyName("total_downloaded_session")]
|
||||
public Int64 TotalDownloadedSession { get; set; }
|
||||
[JsonPropertyName("total_downloaded_session")]
|
||||
public Int64 TotalDownloadedSession { get; set; }
|
||||
|
||||
[JsonPropertyName("total_size")]
|
||||
public Int64 TotalSize { get; set; }
|
||||
[JsonPropertyName("total_size")]
|
||||
public Int64 TotalSize { get; set; }
|
||||
|
||||
[JsonPropertyName("total_uploaded")]
|
||||
public Int64 TotalUploaded { get; set; }
|
||||
[JsonPropertyName("total_uploaded")]
|
||||
public Int64 TotalUploaded { get; set; }
|
||||
|
||||
[JsonPropertyName("total_uploaded_session")]
|
||||
public Int64 TotalUploadedSession { get; set; }
|
||||
[JsonPropertyName("total_uploaded_session")]
|
||||
public Int64 TotalUploadedSession { get; set; }
|
||||
|
||||
[JsonPropertyName("total_wasted")]
|
||||
public Int64 TotalWasted { get; set; }
|
||||
[JsonPropertyName("total_wasted")]
|
||||
public Int64 TotalWasted { get; set; }
|
||||
|
||||
[JsonPropertyName("up_limit")]
|
||||
public Int64 UpLimit { get; set; }
|
||||
[JsonPropertyName("up_limit")]
|
||||
public Int64 UpLimit { get; set; }
|
||||
|
||||
[JsonPropertyName("up_speed")]
|
||||
public Int64 UpSpeed { get; set; }
|
||||
[JsonPropertyName("up_speed")]
|
||||
public Int64 UpSpeed { get; set; }
|
||||
|
||||
[JsonPropertyName("up_speed_avg")]
|
||||
public Int64 UpSpeedAvg { get; set; }
|
||||
}
|
||||
[JsonPropertyName("up_speed_avg")]
|
||||
public Int64 UpSpeedAvg { get; set; }
|
||||
}
|
||||
|
|
@ -1,11 +1,8 @@
|
|||
using System;
|
||||
namespace RdtClient.Data.Models.TorrentClient;
|
||||
|
||||
namespace RdtClient.Data.Models.TorrentClient
|
||||
public class TorrentClientAvailableFile
|
||||
{
|
||||
public class TorrentClientAvailableFile
|
||||
{
|
||||
public String Filename { get; set; }
|
||||
public String Filename { get; set; }
|
||||
|
||||
public Int64 Filesize { get; set; }
|
||||
}
|
||||
}
|
||||
public Int64 Filesize { get; set; }
|
||||
}
|
||||
|
|
@ -1,12 +1,9 @@
|
|||
using System;
|
||||
namespace RdtClient.Data.Models.TorrentClient;
|
||||
|
||||
namespace RdtClient.Data.Models.TorrentClient
|
||||
public class TorrentClientFile
|
||||
{
|
||||
public class TorrentClientFile
|
||||
{
|
||||
public Int64 Id { get; set; }
|
||||
public String Path { get; set; }
|
||||
public Int64 Bytes { get; set; }
|
||||
public Boolean Selected { get; set; }
|
||||
}
|
||||
}
|
||||
public Int64 Id { get; set; }
|
||||
public String Path { get; set; }
|
||||
public Int64 Bytes { get; set; }
|
||||
public Boolean Selected { get; set; }
|
||||
}
|
||||
|
|
@ -1,26 +1,22 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
namespace RdtClient.Data.Models.TorrentClient;
|
||||
|
||||
namespace RdtClient.Data.Models.TorrentClient
|
||||
public class TorrentClientTorrent
|
||||
{
|
||||
public class TorrentClientTorrent
|
||||
{
|
||||
public String Id { get; set; }
|
||||
public String Filename { get; set; }
|
||||
public String OriginalFilename { get; set; }
|
||||
public String Hash { get; set; }
|
||||
public Int64 Bytes { get; set; }
|
||||
public Int64 OriginalBytes { get; set; }
|
||||
public String Host { get; set; }
|
||||
public Int64 Split { get; set; }
|
||||
public Int64 Progress { get; set; }
|
||||
public String Status { get; set; }
|
||||
public Int64 StatusCode { get; set; }
|
||||
public DateTimeOffset Added { get; set; }
|
||||
public List<TorrentClientFile> Files { get; set; }
|
||||
public List<String> Links { get; set; }
|
||||
public DateTimeOffset? Ended { get; set; }
|
||||
public Int64? Speed { get; set; }
|
||||
public Int64? Seeders { get; set; }
|
||||
}
|
||||
}
|
||||
public String Id { get; set; }
|
||||
public String Filename { get; set; }
|
||||
public String OriginalFilename { get; set; }
|
||||
public String Hash { get; set; }
|
||||
public Int64 Bytes { get; set; }
|
||||
public Int64 OriginalBytes { get; set; }
|
||||
public String Host { get; set; }
|
||||
public Int64 Split { get; set; }
|
||||
public Int64 Progress { get; set; }
|
||||
public String Status { get; set; }
|
||||
public Int64 StatusCode { get; set; }
|
||||
public DateTimeOffset Added { get; set; }
|
||||
public List<TorrentClientFile> Files { get; set; }
|
||||
public List<String> Links { get; set; }
|
||||
public DateTimeOffset? Ended { get; set; }
|
||||
public Int64? Speed { get; set; }
|
||||
public Int64? Seeders { get; set; }
|
||||
}
|
||||
|
|
@ -1,10 +1,7 @@
|
|||
using System;
|
||||
namespace RdtClient.Data.Models.TorrentClient;
|
||||
|
||||
namespace RdtClient.Data.Models.TorrentClient
|
||||
public class TorrentClientUser
|
||||
{
|
||||
public class TorrentClientUser
|
||||
{
|
||||
public String Username { get; set; }
|
||||
public DateTimeOffset? Expiration { get; set; }
|
||||
}
|
||||
}
|
||||
public String Username { get; set; }
|
||||
public DateTimeOffset? Expiration { get; set; }
|
||||
}
|
||||
|
|
@ -2,18 +2,21 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Nullable>disable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.3" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="6.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="6.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.3">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Serilog" Version="2.10.0" />
|
||||
<PackageReference Include="Serilog" Version="2.11.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -2,23 +2,24 @@
|
|||
using RdtClient.Service.Services;
|
||||
using RdtClient.Service.Services.TorrentClients;
|
||||
|
||||
namespace RdtClient.Service
|
||||
namespace RdtClient.Service;
|
||||
|
||||
public static class DiConfig
|
||||
{
|
||||
public static class DiConfig
|
||||
public static void Config(IServiceCollection services)
|
||||
{
|
||||
public static void Config(IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<AllDebridTorrentClient>();
|
||||
services.AddScoped<Authentication>();
|
||||
services.AddScoped<Downloads>();
|
||||
services.AddScoped<QBittorrent>();
|
||||
services.AddScoped<RemoteService>();
|
||||
services.AddScoped<RealDebridTorrentClient>();
|
||||
services.AddScoped<Settings>();
|
||||
services.AddScoped<Torrents>();
|
||||
services.AddScoped<TorrentRunner>();
|
||||
services.AddScoped<AllDebridTorrentClient>();
|
||||
services.AddScoped<Authentication>();
|
||||
services.AddScoped<Downloads>();
|
||||
services.AddScoped<QBittorrent>();
|
||||
services.AddScoped<RemoteService>();
|
||||
services.AddScoped<RealDebridTorrentClient>();
|
||||
services.AddScoped<Settings>();
|
||||
services.AddScoped<Torrents>();
|
||||
services.AddScoped<TorrentRunner>();
|
||||
|
||||
services.AddHostedService<Startup>();
|
||||
}
|
||||
services.AddHostedService<Startup>();
|
||||
services.AddHostedService<TaskRunner>();
|
||||
services.AddHostedService<UpdateChecker>();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +1,33 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using System.Web;
|
||||
using RdtClient.Data.Models.Data;
|
||||
|
||||
namespace RdtClient.Service.Helpers
|
||||
namespace RdtClient.Service.Helpers;
|
||||
|
||||
public static class DownloadHelper
|
||||
{
|
||||
public static class DownloadHelper
|
||||
public static String GetDownloadPath(String downloadPath, Torrent torrent, Download download)
|
||||
{
|
||||
public static String GetDownloadPath(String downloadPath, Torrent torrent, Download download)
|
||||
var fileUrl = download.Link;
|
||||
|
||||
if (String.IsNullOrWhiteSpace(fileUrl))
|
||||
{
|
||||
var fileUrl = download.Link;
|
||||
|
||||
if (String.IsNullOrWhiteSpace(fileUrl))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var uri = new Uri(fileUrl);
|
||||
var torrentPath = Path.Combine(downloadPath, torrent.RdName);
|
||||
|
||||
if (!Directory.Exists(torrentPath))
|
||||
{
|
||||
Directory.CreateDirectory(torrentPath);
|
||||
}
|
||||
|
||||
var fileName = uri.Segments.Last();
|
||||
|
||||
fileName = HttpUtility.UrlDecode(fileName);
|
||||
|
||||
var filePath = Path.Combine(torrentPath, fileName);
|
||||
|
||||
return filePath;
|
||||
return null;
|
||||
}
|
||||
|
||||
var uri = new Uri(fileUrl);
|
||||
var torrentPath = Path.Combine(downloadPath, torrent.RdName);
|
||||
|
||||
if (!Directory.Exists(torrentPath))
|
||||
{
|
||||
Directory.CreateDirectory(torrentPath);
|
||||
}
|
||||
|
||||
var fileName = uri.Segments.Last();
|
||||
|
||||
fileName = HttpUtility.UrlDecode(fileName);
|
||||
|
||||
var filePath = Path.Combine(torrentPath, fileName);
|
||||
|
||||
return filePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,45 +1,40 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
namespace RdtClient.Service.Helpers;
|
||||
|
||||
namespace RdtClient.Service.Helpers
|
||||
public static class FileHelper
|
||||
{
|
||||
public static class FileHelper
|
||||
public static async Task Delete(String path)
|
||||
{
|
||||
public static async Task Delete(String path)
|
||||
if (String.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(path))
|
||||
return;
|
||||
}
|
||||
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var retry = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
return;
|
||||
File.Delete(path);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (!File.Exists(path))
|
||||
catch
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var retry = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
if (retry >= 3)
|
||||
{
|
||||
File.Delete(path);
|
||||
|
||||
break;
|
||||
throw;
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (retry >= 3)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
retry++;
|
||||
retry++;
|
||||
|
||||
await Task.Delay(1000 * retry);
|
||||
}
|
||||
await Task.Delay(1000 * retry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,33 +1,30 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
namespace RdtClient.Service.Helpers
|
||||
namespace RdtClient.Service.Helpers;
|
||||
|
||||
public class JsonModelBinder : IModelBinder
|
||||
{
|
||||
public class JsonModelBinder : IModelBinder
|
||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
{
|
||||
public Task BindModelAsync(ModelBindingContext bindingContext)
|
||||
if (bindingContext == null)
|
||||
{
|
||||
if (bindingContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(bindingContext));
|
||||
}
|
||||
|
||||
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
|
||||
if (valueProviderResult != ValueProviderResult.None)
|
||||
{
|
||||
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
|
||||
|
||||
var valueAsString = valueProviderResult.FirstValue;
|
||||
var result = Newtonsoft.Json.JsonConvert.DeserializeObject(valueAsString, bindingContext.ModelType);
|
||||
if (result != null)
|
||||
{
|
||||
bindingContext.Result = ModelBindingResult.Success(result);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
throw new ArgumentNullException(nameof(bindingContext));
|
||||
}
|
||||
|
||||
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
|
||||
if (valueProviderResult != ValueProviderResult.None)
|
||||
{
|
||||
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
|
||||
|
||||
var valueAsString = valueProviderResult.FirstValue;
|
||||
var result = Newtonsoft.Json.JsonConvert.DeserializeObject(valueAsString, bindingContext.ModelType);
|
||||
if (result != null)
|
||||
{
|
||||
bindingContext.Result = ModelBindingResult.Success(result);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +1,28 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using System.Web;
|
||||
using RdtClient.Data.Models.Data;
|
||||
|
||||
namespace RdtClient.Service.Helpers
|
||||
namespace RdtClient.Service.Helpers;
|
||||
|
||||
public static class Logger
|
||||
{
|
||||
public static class Logger
|
||||
public static String ToLog(this Download download)
|
||||
{
|
||||
public static String ToLog(this Download download)
|
||||
var fileName = download.Path;
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(download.Link))
|
||||
{
|
||||
var fileName = download.Path;
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(download.Link))
|
||||
{
|
||||
var uri = new Uri(download.Link);
|
||||
fileName = uri.Segments.Last();
|
||||
fileName = HttpUtility.UrlDecode(fileName);
|
||||
}
|
||||
|
||||
var done = (Int32)((Double)download.BytesDone / download.BytesTotal) * 100;
|
||||
|
||||
return $"for download {fileName} {done}% ({download.DownloadId})";
|
||||
var uri = new Uri(download.Link);
|
||||
fileName = uri.Segments.Last();
|
||||
fileName = HttpUtility.UrlDecode(fileName);
|
||||
}
|
||||
|
||||
public static String ToLog(this Torrent torrent)
|
||||
{
|
||||
return $"for torrent {torrent.RdName} ({torrent.RdId} - {torrent.RdStatusRaw} {torrent.RdProgress}%) ({torrent.TorrentId})";
|
||||
}
|
||||
var done = (Int32)((Double)download.BytesDone / download.BytesTotal) * 100;
|
||||
|
||||
return $"for download {fileName} {done}% ({download.DownloadId})";
|
||||
}
|
||||
}
|
||||
|
||||
public static String ToLog(this Torrent torrent)
|
||||
{
|
||||
return $"for torrent {torrent.RdName} ({torrent.RdId} - {torrent.RdStatusRaw} {torrent.RdProgress}%) ({torrent.TorrentId})";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +1,30 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using RdtClient.Data.Models.Data;
|
||||
using RdtClient.Data.Models.Data;
|
||||
|
||||
namespace RdtClient.Service.Helpers
|
||||
namespace RdtClient.Service.Helpers;
|
||||
|
||||
public static class SettingsHelper
|
||||
{
|
||||
public static class SettingsHelper
|
||||
public static String GetString(this IList<Setting> settings, String key)
|
||||
{
|
||||
public static String GetString(this IList<Setting> settings, String key)
|
||||
var setting = settings.FirstOrDefault(m => m.SettingId == key);
|
||||
|
||||
if (setting == null)
|
||||
{
|
||||
var setting = settings.FirstOrDefault(m => m.SettingId == key);
|
||||
|
||||
if (setting == null)
|
||||
{
|
||||
throw new Exception($"Setting with key {key} not found");
|
||||
}
|
||||
|
||||
return setting.Value;
|
||||
throw new Exception($"Setting with key {key} not found");
|
||||
}
|
||||
|
||||
public static Int32 GetNumber(this IList<Setting> settings, String key)
|
||||
{
|
||||
var setting = settings.FirstOrDefault(m => m.SettingId == key);
|
||||
|
||||
if (setting == null)
|
||||
{
|
||||
throw new Exception($"Setting with key {key} not found");
|
||||
}
|
||||
|
||||
return Int32.Parse(setting.Value);
|
||||
}
|
||||
return setting.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public static Int32 GetNumber(this IList<Setting> settings, String key)
|
||||
{
|
||||
var setting = settings.FirstOrDefault(m => m.SettingId == key);
|
||||
|
||||
if (setting == null)
|
||||
{
|
||||
throw new Exception($"Setting with key {key} not found");
|
||||
}
|
||||
|
||||
return Int32.Parse(setting.Value);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +1,29 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace RdtClient.Service.Middleware
|
||||
namespace RdtClient.Service.Middleware;
|
||||
|
||||
public class AuthorizeMiddleware
|
||||
{
|
||||
public class AuthorizeMiddleware
|
||||
private readonly RequestDelegate _next;
|
||||
|
||||
public AuthorizeMiddleware(RequestDelegate next)
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
_next = next;
|
||||
}
|
||||
|
||||
public AuthorizeMiddleware(RequestDelegate next)
|
||||
/// <summary>
|
||||
/// Return a 403 instead of a 401, it's quirk that QBittorrent has.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public async Task Invoke(HttpContext context)
|
||||
{
|
||||
await _next(context);
|
||||
|
||||
if (context.Response.StatusCode == (Int32) HttpStatusCode.Unauthorized)
|
||||
{
|
||||
_next = next;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a 403 instead of a 401, it's quirk that QBittorrent has.
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
public async Task Invoke(HttpContext context)
|
||||
{
|
||||
await _next(context);
|
||||
|
||||
if (context.Response.StatusCode == (Int32) HttpStatusCode.Unauthorized)
|
||||
{
|
||||
context.Response.StatusCode = (Int32) HttpStatusCode.Forbidden;
|
||||
}
|
||||
context.Response.StatusCode = (Int32) HttpStatusCode.Forbidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +1,27 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Diagnostics;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace RdtClient.Service.Middleware
|
||||
{
|
||||
public static class ExceptionMiddlewareExtensions
|
||||
{
|
||||
public static void ConfigureExceptionHandler(this IApplicationBuilder app)
|
||||
{
|
||||
app.UseExceptionHandler(appError =>
|
||||
{
|
||||
appError.Run(async context =>
|
||||
{
|
||||
context.Response.StatusCode = (Int32) HttpStatusCode.InternalServerError;
|
||||
context.Response.ContentType = "application/json";
|
||||
namespace RdtClient.Service.Middleware;
|
||||
|
||||
var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
|
||||
if (contextFeature != null)
|
||||
{
|
||||
await context.Response.WriteAsync(contextFeature.Error.Message);
|
||||
}
|
||||
});
|
||||
public static class ExceptionMiddlewareExtensions
|
||||
{
|
||||
public static void ConfigureExceptionHandler(this IApplicationBuilder app)
|
||||
{
|
||||
app.UseExceptionHandler(appError =>
|
||||
{
|
||||
appError.Run(async context =>
|
||||
{
|
||||
context.Response.StatusCode = (Int32) HttpStatusCode.InternalServerError;
|
||||
context.Response.ContentType = "application/json";
|
||||
|
||||
var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
|
||||
if (contextFeature != null)
|
||||
{
|
||||
await context.Response.WriteAsync(contextFeature.Error.Message);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +1,23 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<OutputType>library</OutputType>
|
||||
<Nullable>disable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="AllDebrid.NET" Version="1.0.7" />
|
||||
<PackageReference Include="Aria2.NET" Version="1.0.4" />
|
||||
<PackageReference Include="Downloader" Version="2.3.3" />
|
||||
<PackageReference Include="MonoTorrent" Version="2.0.4" />
|
||||
<PackageReference Include="MonoTorrent" Version="2.0.5" />
|
||||
<PackageReference Include="RD.NET" Version="2.1.2" />
|
||||
<PackageReference Include="Serilog" Version="2.10.0" />
|
||||
<PackageReference Include="Serilog" Version="2.11.0" />
|
||||
<PackageReference Include="Serilog.Exceptions" Version="8.1.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageReference Include="SharpCompress" Version="0.30.1" />
|
||||
<PackageReference Include="SharpCompress" Version="0.31.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,79 +1,76 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using RdtClient.Data.Data;
|
||||
|
||||
namespace RdtClient.Service.Services
|
||||
namespace RdtClient.Service.Services;
|
||||
|
||||
public class Authentication
|
||||
{
|
||||
public class Authentication
|
||||
private readonly SignInManager<IdentityUser> _signInManager;
|
||||
private readonly UserManager<IdentityUser> _userManager;
|
||||
private readonly UserData _userData;
|
||||
|
||||
public Authentication(SignInManager<IdentityUser> signInManager, UserManager<IdentityUser> userManager, UserData userData)
|
||||
{
|
||||
private readonly SignInManager<IdentityUser> _signInManager;
|
||||
private readonly UserManager<IdentityUser> _userManager;
|
||||
private readonly UserData _userData;
|
||||
|
||||
public Authentication(SignInManager<IdentityUser> signInManager, UserManager<IdentityUser> userManager, UserData userData)
|
||||
{
|
||||
_signInManager = signInManager;
|
||||
_userManager = userManager;
|
||||
_userData = userData;
|
||||
}
|
||||
|
||||
public async Task<IdentityResult> Register(String userName, String password)
|
||||
{
|
||||
var user = new IdentityUser(userName);
|
||||
|
||||
var result = await _userManager.CreateAsync(user, password);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<SignInResult> Login(String userName, String password)
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(userName) || String.IsNullOrWhiteSpace(password))
|
||||
{
|
||||
return SignInResult.Failed;
|
||||
}
|
||||
|
||||
var result = await _signInManager.PasswordSignInAsync(userName, password, true, false);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<IdentityUser> GetUser()
|
||||
{
|
||||
return await _userData.GetUser();
|
||||
}
|
||||
|
||||
public async Task Logout()
|
||||
{
|
||||
await _signInManager.SignOutAsync();
|
||||
}
|
||||
|
||||
public async Task<IdentityResult> Update(String newUserName, String newPassword)
|
||||
{
|
||||
var user = await GetUser();
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
throw new Exception("No logged in user found");
|
||||
}
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(newUserName))
|
||||
{
|
||||
user.UserName = newUserName;
|
||||
}
|
||||
|
||||
await _userManager.UpdateAsync(user);
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(newPassword))
|
||||
{
|
||||
var token = await _userManager.GeneratePasswordResetTokenAsync(user);
|
||||
var result = await _userManager.ResetPasswordAsync(user, token, newPassword);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return IdentityResult.Success;
|
||||
}
|
||||
_signInManager = signInManager;
|
||||
_userManager = userManager;
|
||||
_userData = userData;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IdentityResult> Register(String userName, String password)
|
||||
{
|
||||
var user = new IdentityUser(userName);
|
||||
|
||||
var result = await _userManager.CreateAsync(user, password);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<SignInResult> Login(String userName, String password)
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(userName) || String.IsNullOrWhiteSpace(password))
|
||||
{
|
||||
return SignInResult.Failed;
|
||||
}
|
||||
|
||||
var result = await _signInManager.PasswordSignInAsync(userName, password, true, false);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<IdentityUser> GetUser()
|
||||
{
|
||||
return await _userData.GetUser();
|
||||
}
|
||||
|
||||
public async Task Logout()
|
||||
{
|
||||
await _signInManager.SignOutAsync();
|
||||
}
|
||||
|
||||
public async Task<IdentityResult> Update(String newUserName, String newPassword)
|
||||
{
|
||||
var user = await GetUser();
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
throw new Exception("No logged in user found");
|
||||
}
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(newUserName))
|
||||
{
|
||||
user.UserName = newUserName;
|
||||
}
|
||||
|
||||
await _userManager.UpdateAsync(user);
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(newPassword))
|
||||
{
|
||||
var token = await _userManager.GeneratePasswordResetTokenAsync(user);
|
||||
var result = await _userManager.ResetPasswordAsync(user, token, newPassword);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return IdentityResult.Success;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,126 +1,123 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using RdtClient.Data.Models.Data;
|
||||
using RdtClient.Data.Models.Data;
|
||||
using RdtClient.Data.Models.Internal;
|
||||
using RdtClient.Service.Helpers;
|
||||
using RdtClient.Service.Services.Downloaders;
|
||||
|
||||
namespace RdtClient.Service.Services
|
||||
namespace RdtClient.Service.Services;
|
||||
|
||||
public class DownloadClient
|
||||
{
|
||||
public class DownloadClient
|
||||
private readonly String _destinationPath;
|
||||
|
||||
private readonly Download _download;
|
||||
private readonly Torrent _torrent;
|
||||
|
||||
public IDownloader Downloader;
|
||||
|
||||
public DownloadClient(Download download, Torrent torrent, String destinationPath)
|
||||
{
|
||||
private readonly String _destinationPath;
|
||||
_download = download;
|
||||
_torrent = torrent;
|
||||
_destinationPath = destinationPath;
|
||||
}
|
||||
|
||||
private readonly Download _download;
|
||||
private readonly Torrent _torrent;
|
||||
public String Type { get; set; }
|
||||
|
||||
public IDownloader Downloader;
|
||||
public Boolean Finished { get; private set; }
|
||||
|
||||
public DownloadClient(Download download, Torrent torrent, String destinationPath)
|
||||
public String Error { get; private set; }
|
||||
|
||||
public Int64 Speed { get; private set; }
|
||||
public Int64 BytesTotal { get; private set; }
|
||||
public Int64 BytesDone { get; private set; }
|
||||
|
||||
public async Task<String> Start(DbSettings settings)
|
||||
{
|
||||
BytesDone = 0;
|
||||
BytesTotal = 0;
|
||||
Speed = 0;
|
||||
|
||||
try
|
||||
{
|
||||
_download = download;
|
||||
_torrent = torrent;
|
||||
_destinationPath = destinationPath;
|
||||
}
|
||||
var filePath = DownloadHelper.GetDownloadPath(_destinationPath, _torrent, _download);
|
||||
|
||||
public String Type { get; set; }
|
||||
|
||||
public Boolean Finished { get; private set; }
|
||||
|
||||
public String Error { get; private set; }
|
||||
|
||||
public Int64 Speed { get; private set; }
|
||||
public Int64 BytesTotal { get; private set; }
|
||||
public Int64 BytesDone { get; private set; }
|
||||
|
||||
public async Task<String> Start(DbSettings settings)
|
||||
{
|
||||
BytesDone = 0;
|
||||
BytesTotal = 0;
|
||||
Speed = 0;
|
||||
|
||||
try
|
||||
if (filePath == null)
|
||||
{
|
||||
var filePath = DownloadHelper.GetDownloadPath(_destinationPath, _torrent, _download);
|
||||
|
||||
if (filePath == null)
|
||||
{
|
||||
throw new Exception("Invalid download path");
|
||||
}
|
||||
|
||||
await FileHelper.Delete(filePath);
|
||||
|
||||
Type = settings.DownloadClient;
|
||||
|
||||
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}")
|
||||
};
|
||||
|
||||
Downloader.DownloadComplete += (_, args) =>
|
||||
{
|
||||
Finished = true;
|
||||
Error ??= args.Error;
|
||||
};
|
||||
|
||||
Downloader.DownloadProgress += (_, args) =>
|
||||
{
|
||||
Speed = args.Speed;
|
||||
BytesDone = args.BytesDone;
|
||||
BytesTotal = args.BytesTotal;
|
||||
};
|
||||
|
||||
var result = await Downloader.Download();
|
||||
|
||||
await Task.Delay(1000);
|
||||
|
||||
return result;
|
||||
throw new Exception("Invalid download path");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (Downloader != null)
|
||||
{
|
||||
await Downloader.Cancel();
|
||||
}
|
||||
|
||||
await FileHelper.Delete(filePath);
|
||||
|
||||
Error = $"An unexpected error occurred preparing download {_download.Link} for torrent {_torrent.RdName}: {ex.Message}";
|
||||
Type = settings.DownloadClient;
|
||||
|
||||
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}")
|
||||
};
|
||||
|
||||
Downloader.DownloadComplete += (_, args) =>
|
||||
{
|
||||
Finished = true;
|
||||
Error ??= args.Error;
|
||||
};
|
||||
|
||||
return null;
|
||||
}
|
||||
Downloader.DownloadProgress += (_, args) =>
|
||||
{
|
||||
Speed = args.Speed;
|
||||
BytesDone = args.BytesDone;
|
||||
BytesTotal = args.BytesTotal;
|
||||
};
|
||||
|
||||
var result = await Downloader.Download();
|
||||
|
||||
await Task.Delay(1000);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task Cancel()
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (Downloader != null)
|
||||
{
|
||||
await Downloader.Cancel();
|
||||
}
|
||||
|
||||
Error = $"An unexpected error occurred preparing download {_download.Link} for torrent {_torrent.RdName}: {ex.Message}";
|
||||
Finished = true;
|
||||
Error = null;
|
||||
|
||||
if (Downloader == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
await Downloader.Cancel();
|
||||
}
|
||||
|
||||
public async Task Pause()
|
||||
{
|
||||
if (Downloader == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
await Downloader.Pause();
|
||||
}
|
||||
|
||||
public async Task Resume()
|
||||
{
|
||||
if (Downloader == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
await Downloader.Resume();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Cancel()
|
||||
{
|
||||
Finished = true;
|
||||
Error = null;
|
||||
|
||||
if (Downloader == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
await Downloader.Cancel();
|
||||
}
|
||||
|
||||
public async Task Pause()
|
||||
{
|
||||
if (Downloader == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
await Downloader.Pause();
|
||||
}
|
||||
|
||||
public async Task Resume()
|
||||
{
|
||||
if (Downloader == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
await Downloader.Resume();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,272 +1,265 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Aria2NET;
|
||||
using Aria2NET;
|
||||
using RdtClient.Data.Models.Internal;
|
||||
using Serilog;
|
||||
|
||||
namespace RdtClient.Service.Services.Downloaders
|
||||
namespace RdtClient.Service.Services.Downloaders;
|
||||
|
||||
public class Aria2cDownloader : IDownloader
|
||||
{
|
||||
public class Aria2cDownloader : IDownloader
|
||||
public event EventHandler<DownloadCompleteEventArgs> DownloadComplete;
|
||||
public event EventHandler<DownloadProgressEventArgs> DownloadProgress;
|
||||
|
||||
private const Int32 RetryCount = 5;
|
||||
|
||||
private readonly Aria2NetClient _aria2NetClient;
|
||||
|
||||
private readonly ILogger _logger;
|
||||
private readonly String _uri;
|
||||
private readonly String _filePath;
|
||||
|
||||
private String _gid;
|
||||
|
||||
public Aria2cDownloader(String gid, String uri, String filePath, DbSettings settings)
|
||||
{
|
||||
public event EventHandler<DownloadCompleteEventArgs> DownloadComplete;
|
||||
public event EventHandler<DownloadProgressEventArgs> DownloadProgress;
|
||||
_logger = Log.ForContext<Aria2cDownloader>();
|
||||
_gid = gid;
|
||||
_uri = uri;
|
||||
_filePath = filePath;
|
||||
|
||||
private const Int32 RetryCount = 5;
|
||||
|
||||
private readonly Aria2NetClient _aria2NetClient;
|
||||
|
||||
private readonly ILogger _logger;
|
||||
private readonly String _uri;
|
||||
private readonly String _filePath;
|
||||
|
||||
private String _gid;
|
||||
|
||||
public Aria2cDownloader(String gid, String uri, String filePath, DbSettings settings)
|
||||
var httpClient = new HttpClient
|
||||
{
|
||||
_logger = Log.ForContext<Aria2cDownloader>();
|
||||
_gid = gid;
|
||||
_uri = uri;
|
||||
_filePath = filePath;
|
||||
Timeout = TimeSpan.FromSeconds(10)
|
||||
};
|
||||
|
||||
var httpClient = new HttpClient
|
||||
{
|
||||
Timeout = TimeSpan.FromSeconds(10)
|
||||
};
|
||||
|
||||
_aria2NetClient = new Aria2NetClient(settings.Aria2cUrl, settings.Aria2cSecret, httpClient, 10);
|
||||
}
|
||||
_aria2NetClient = new Aria2NetClient(settings.Aria2cUrl, settings.Aria2cSecret, httpClient, 10);
|
||||
}
|
||||
|
||||
public async Task<String> Download()
|
||||
public async Task<String> Download()
|
||||
{
|
||||
var path = Path.GetDirectoryName(_filePath);
|
||||
var fileName = Path.GetFileName(_filePath);
|
||||
|
||||
_logger.Debug($"Starting download of {_uri}, writing to path: {path}, fileName: {fileName}");
|
||||
|
||||
if (String.IsNullOrWhiteSpace(_gid))
|
||||
{
|
||||
var path = Path.GetDirectoryName(_filePath);
|
||||
var fileName = Path.GetFileName(_filePath);
|
||||
var isAlreadyAdded = await CheckIfAdded();
|
||||
|
||||
_logger.Debug($"Starting download of {_uri}, writing to path: {path}, fileName: {fileName}");
|
||||
|
||||
if (String.IsNullOrWhiteSpace(_gid))
|
||||
if (isAlreadyAdded)
|
||||
{
|
||||
var isAlreadyAdded = await CheckIfAdded();
|
||||
|
||||
if (isAlreadyAdded)
|
||||
{
|
||||
throw new Exception($"The download link {_uri} has already been added to Aria2");
|
||||
}
|
||||
throw new Exception($"The download link {_uri} has already been added to Aria2");
|
||||
}
|
||||
}
|
||||
|
||||
var retryCount = 0;
|
||||
while(true)
|
||||
var retryCount = 0;
|
||||
while(true)
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
if (_gid != null)
|
||||
{
|
||||
if (_gid != null)
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
await _aria2NetClient.TellStatus(_gid);
|
||||
await _aria2NetClient.TellStatus(_gid);
|
||||
|
||||
return _gid;
|
||||
}
|
||||
catch
|
||||
{
|
||||
_gid = null;
|
||||
}
|
||||
return _gid;
|
||||
}
|
||||
catch
|
||||
{
|
||||
_gid = null;
|
||||
}
|
||||
}
|
||||
|
||||
_gid ??= await _aria2NetClient.AddUri(new List<String>
|
||||
_gid ??= await _aria2NetClient.AddUri(new List<String>
|
||||
{
|
||||
_uri
|
||||
},
|
||||
new Dictionary<String, Object>
|
||||
{
|
||||
{
|
||||
_uri
|
||||
"dir", path
|
||||
},
|
||||
new Dictionary<String, Object>
|
||||
{
|
||||
{
|
||||
"dir", path
|
||||
},
|
||||
{
|
||||
"out", fileName
|
||||
}
|
||||
});
|
||||
"out", fileName
|
||||
}
|
||||
});
|
||||
|
||||
_logger.Debug($"Added download to Aria2, received ID {_gid}");
|
||||
_logger.Debug($"Added download to Aria2, received ID {_gid}");
|
||||
|
||||
await _aria2NetClient.TellStatus(_gid);
|
||||
await _aria2NetClient.TellStatus(_gid);
|
||||
|
||||
_logger.Debug($"Download with ID {_gid} found in Aria2");
|
||||
_logger.Debug($"Download with ID {_gid} found in Aria2");
|
||||
|
||||
return _gid;
|
||||
return _gid;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (retryCount >= RetryCount)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (retryCount >= RetryCount)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
_logger.Debug($"Error starting download: {ex.Message}. Retrying {retryCount}/{RetryCount}");
|
||||
_logger.Debug($"Error starting download: {ex.Message}. Retrying {retryCount}/{RetryCount}");
|
||||
|
||||
await Task.Delay(retryCount * 1000);
|
||||
await Task.Delay(retryCount * 1000);
|
||||
|
||||
retryCount++;
|
||||
}
|
||||
retryCount++;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Cancel()
|
||||
{
|
||||
await Remove();
|
||||
}
|
||||
|
||||
public async Task Pause()
|
||||
{
|
||||
_logger.Debug($"Pausing download {_uri} {_gid}");
|
||||
|
||||
if (String.IsNullOrWhiteSpace(_gid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _aria2NetClient.Pause(_gid);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Resume()
|
||||
{
|
||||
_logger.Debug($"Resuming download {_uri} {_gid}");
|
||||
|
||||
if (String.IsNullOrWhiteSpace(_gid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _aria2NetClient.Unpause(_gid);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Update(IEnumerable<DownloadStatusResult> allDownloads)
|
||||
{
|
||||
if (_gid == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var download = allDownloads.FirstOrDefault(m => m.Gid == _gid);
|
||||
|
||||
if (download == null)
|
||||
{
|
||||
DownloadComplete?.Invoke(this, new DownloadCompleteEventArgs
|
||||
{
|
||||
Error = $"Download was not found in Aria2"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(download.ErrorMessage) || download.Status == "error")
|
||||
{
|
||||
await Remove();
|
||||
DownloadComplete?.Invoke(this, new DownloadCompleteEventArgs
|
||||
{
|
||||
Error = $"{download.ErrorCode}: {download.ErrorMessage}"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (download.Status == "complete" || download.Status == "removed")
|
||||
{
|
||||
_logger.Debug($"Aria2 download found as complete {_gid}");
|
||||
|
||||
await Remove();
|
||||
|
||||
var retryCount = 0;
|
||||
while (true)
|
||||
{
|
||||
if (retryCount >= 10)
|
||||
{
|
||||
DownloadComplete?.Invoke(this, new DownloadCompleteEventArgs
|
||||
{
|
||||
Error = $"File not found at {_filePath}"
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
if (File.Exists(_filePath))
|
||||
{
|
||||
DownloadComplete?.Invoke(this, new DownloadCompleteEventArgs());
|
||||
break;
|
||||
}
|
||||
|
||||
await Task.Delay(1000 * retryCount);
|
||||
retryCount++;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
DownloadProgress?.Invoke(this, new DownloadProgressEventArgs
|
||||
{
|
||||
BytesDone = download.CompletedLength,
|
||||
BytesTotal = download.TotalLength,
|
||||
Speed = download.DownloadSpeed
|
||||
});
|
||||
}
|
||||
|
||||
private async Task Remove()
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(_gid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Debug($"Remove download {_uri} {_gid} from Aria2");
|
||||
|
||||
try
|
||||
{
|
||||
await _aria2NetClient.ForceRemove(_gid);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _aria2NetClient.RemoveDownloadResult(_gid);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Boolean> CheckIfAdded()
|
||||
{
|
||||
var allDownloads = await _aria2NetClient.TellAll();
|
||||
|
||||
foreach (var download in allDownloads.Where(m => m.Files != null))
|
||||
{
|
||||
foreach (var file in download.Files.Where(m => m.Uris != null))
|
||||
{
|
||||
if (file.Uris.Any(uri => uri.Uri == _uri))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Cancel()
|
||||
{
|
||||
await Remove();
|
||||
}
|
||||
|
||||
public async Task Pause()
|
||||
{
|
||||
_logger.Debug($"Pausing download {_uri} {_gid}");
|
||||
|
||||
if (String.IsNullOrWhiteSpace(_gid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _aria2NetClient.Pause(_gid);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Resume()
|
||||
{
|
||||
_logger.Debug($"Resuming download {_uri} {_gid}");
|
||||
|
||||
if (String.IsNullOrWhiteSpace(_gid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _aria2NetClient.Unpause(_gid);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Update(IEnumerable<DownloadStatusResult> allDownloads)
|
||||
{
|
||||
if (_gid == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var download = allDownloads.FirstOrDefault(m => m.Gid == _gid);
|
||||
|
||||
if (download == null)
|
||||
{
|
||||
DownloadComplete.Invoke(this, new DownloadCompleteEventArgs
|
||||
{
|
||||
Error = $"Download was not found in Aria2"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(download.ErrorMessage) || download.Status == "error")
|
||||
{
|
||||
await Remove();
|
||||
DownloadComplete.Invoke(this, new DownloadCompleteEventArgs
|
||||
{
|
||||
Error = $"{download.ErrorCode}: {download.ErrorMessage}"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (download.Status == "complete" || download.Status == "removed")
|
||||
{
|
||||
_logger.Debug($"Aria2 download found as complete {_gid}");
|
||||
|
||||
await Remove();
|
||||
|
||||
var retryCount = 0;
|
||||
while (true)
|
||||
{
|
||||
if (retryCount >= 10)
|
||||
{
|
||||
DownloadComplete.Invoke(this, new DownloadCompleteEventArgs
|
||||
{
|
||||
Error = $"File not found at {_filePath}"
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
if (File.Exists(_filePath))
|
||||
{
|
||||
DownloadComplete.Invoke(this, new DownloadCompleteEventArgs());
|
||||
break;
|
||||
}
|
||||
|
||||
await Task.Delay(1000 * retryCount);
|
||||
retryCount++;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
DownloadProgress.Invoke(this, new DownloadProgressEventArgs
|
||||
{
|
||||
BytesDone = download.CompletedLength,
|
||||
BytesTotal = download.TotalLength,
|
||||
Speed = download.DownloadSpeed
|
||||
});
|
||||
}
|
||||
|
||||
private async Task Remove()
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(_gid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Debug($"Remove download {_uri} {_gid} from Aria2");
|
||||
|
||||
try
|
||||
{
|
||||
await _aria2NetClient.ForceRemove(_gid);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _aria2NetClient.RemoveDownloadResult(_gid);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Boolean> CheckIfAdded()
|
||||
{
|
||||
var allDownloads = await _aria2NetClient.TellAll();
|
||||
|
||||
foreach (var download in allDownloads.Where(m => m.Files != null))
|
||||
{
|
||||
foreach (var file in download.Files.Where(m => m.Uris != null))
|
||||
{
|
||||
if (file.Uris.Any(uri => uri.Uri == _uri))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +1,23 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
namespace RdtClient.Service.Services.Downloaders;
|
||||
|
||||
namespace RdtClient.Service.Services.Downloaders
|
||||
public class DownloadCompleteEventArgs
|
||||
{
|
||||
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();
|
||||
Task Pause();
|
||||
Task Resume();
|
||||
}
|
||||
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();
|
||||
Task Pause();
|
||||
Task Resume();
|
||||
}
|
||||
|
|
@ -1,147 +1,143 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net;
|
||||
using Downloader;
|
||||
using RdtClient.Data.Models.Internal;
|
||||
using Serilog;
|
||||
|
||||
namespace RdtClient.Service.Services.Downloaders
|
||||
namespace RdtClient.Service.Services.Downloaders;
|
||||
|
||||
public class MultiDownloader : IDownloader
|
||||
{
|
||||
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;
|
||||
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public MultiDownloader(String uri, String filePath, DbSettings settings)
|
||||
{
|
||||
public event EventHandler<DownloadCompleteEventArgs> DownloadComplete;
|
||||
public event EventHandler<DownloadProgressEventArgs> DownloadProgress;
|
||||
_logger = Log.ForContext<MultiDownloader>();
|
||||
|
||||
private readonly DownloadService _downloadService;
|
||||
private readonly String _filePath;
|
||||
private readonly String _uri;
|
||||
_uri = uri;
|
||||
_filePath = filePath;
|
||||
|
||||
private readonly ILogger _logger;
|
||||
var settingTempPath = settings.TempPath;
|
||||
|
||||
public MultiDownloader(String uri, String filePath, DbSettings settings)
|
||||
if (String.IsNullOrWhiteSpace(settingTempPath))
|
||||
{
|
||||
_logger = Log.ForContext<MultiDownloader>();
|
||||
settingTempPath = Path.GetTempPath();
|
||||
}
|
||||
|
||||
_uri = uri;
|
||||
_filePath = filePath;
|
||||
var settingDownloadChunkCount = settings.DownloadChunkCount;
|
||||
|
||||
var settingTempPath = settings.TempPath;
|
||||
if (settingDownloadChunkCount <= 0)
|
||||
{
|
||||
settingDownloadChunkCount = 1;
|
||||
}
|
||||
|
||||
if (String.IsNullOrWhiteSpace(settingTempPath))
|
||||
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 =
|
||||
{
|
||||
settingTempPath = Path.GetTempPath();
|
||||
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;
|
||||
}
|
||||
|
||||
var settingDownloadChunkCount = settings.DownloadChunkCount;
|
||||
DownloadProgress.Invoke(this,
|
||||
new DownloadProgressEventArgs
|
||||
{
|
||||
Speed = (Int64)args.BytesPerSecondSpeed,
|
||||
BytesDone = args.ReceivedBytesSize,
|
||||
BytesTotal = args.TotalBytesToReceive
|
||||
});
|
||||
};
|
||||
|
||||
if (settingDownloadChunkCount <= 0)
|
||||
_downloadService.DownloadFileCompleted += (_, args) =>
|
||||
{
|
||||
String error = null;
|
||||
|
||||
if (args.Cancelled)
|
||||
{
|
||||
settingDownloadChunkCount = 1;
|
||||
error = $"The download was cancelled";
|
||||
}
|
||||
else if (args.Error != null)
|
||||
{
|
||||
error = args.Error.Message;
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
_logger.Debug($"Starting download of {_uri}, writing to path: {_filePath}");
|
||||
|
||||
await _downloadService.DownloadFileTaskAsync(_uri, _filePath);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Task Cancel()
|
||||
{
|
||||
_logger.Debug($"Cancelling download {_uri}");
|
||||
|
||||
_downloadService.CancelAsync();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task Pause()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task Resume()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
DownloadComplete?.Invoke(this,
|
||||
new DownloadCompleteEventArgs
|
||||
{
|
||||
Error = error
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<String> Download()
|
||||
{
|
||||
_logger.Debug($"Starting download of {_uri}, writing to path: {_filePath}");
|
||||
|
||||
await _downloadService.DownloadFileTaskAsync(_uri, _filePath);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Task Cancel()
|
||||
{
|
||||
_logger.Debug($"Cancelling download {_uri}");
|
||||
|
||||
_downloadService.CancelAsync();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task Pause()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task Resume()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,185 +1,179 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net;
|
||||
using Serilog;
|
||||
|
||||
namespace RdtClient.Service.Services.Downloaders
|
||||
namespace RdtClient.Service.Services.Downloaders;
|
||||
|
||||
public class SimpleDownloader : IDownloader
|
||||
{
|
||||
public class SimpleDownloader : IDownloader
|
||||
private const Int32 BufferSize = 8 * 1024;
|
||||
|
||||
public event EventHandler<DownloadCompleteEventArgs> DownloadComplete;
|
||||
public event EventHandler<DownloadProgressEventArgs> DownloadProgress;
|
||||
|
||||
private readonly String _uri;
|
||||
private readonly String _filePath;
|
||||
|
||||
private Int64 _bytesTotal;
|
||||
private Int64 _bytesDone;
|
||||
|
||||
private Boolean _cancelled;
|
||||
|
||||
private DateTime _nextUpdate;
|
||||
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public SimpleDownloader(String uri, String filePath)
|
||||
{
|
||||
private const Int32 BufferSize = 8 * 1024;
|
||||
_logger = Log.ForContext<SimpleDownloader>();
|
||||
|
||||
public event EventHandler<DownloadCompleteEventArgs> DownloadComplete;
|
||||
public event EventHandler<DownloadProgressEventArgs> DownloadProgress;
|
||||
_uri = uri;
|
||||
_filePath = filePath;
|
||||
}
|
||||
|
||||
private readonly String _uri;
|
||||
private readonly String _filePath;
|
||||
public Task<String> Download()
|
||||
{
|
||||
_logger.Debug($"Starting download of {_uri}, writing to path: {_filePath}");
|
||||
|
||||
private Int64 _bytesTotal;
|
||||
private Int64 _bytesDone;
|
||||
|
||||
private Boolean _cancelled;
|
||||
|
||||
private DateTime _nextUpdate;
|
||||
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public SimpleDownloader(String uri, String filePath)
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
_logger = Log.ForContext<SimpleDownloader>();
|
||||
|
||||
_uri = uri;
|
||||
_filePath = filePath;
|
||||
}
|
||||
|
||||
public Task<String> Download()
|
||||
{
|
||||
_logger.Debug($"Starting download of {_uri}, writing to path: {_filePath}");
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await StartDownloadTask();
|
||||
});
|
||||
await StartDownloadTask();
|
||||
});
|
||||
|
||||
return Task.FromResult<String>(null);
|
||||
}
|
||||
return Task.FromResult<String>(null);
|
||||
}
|
||||
|
||||
public Task Cancel()
|
||||
public Task Cancel()
|
||||
{
|
||||
_logger.Debug($"Cancelling download {_uri}");
|
||||
|
||||
_cancelled = true;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task StartDownloadTask()
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Debug($"Cancelling download {_uri}");
|
||||
_nextUpdate = DateTime.UtcNow.AddSeconds(1);
|
||||
|
||||
_cancelled = true;
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task StartDownloadTask()
|
||||
{
|
||||
try
|
||||
{
|
||||
_nextUpdate = DateTime.UtcNow.AddSeconds(1);
|
||||
|
||||
_bytesTotal = await GetContentSize();
|
||||
_bytesTotal = await GetContentSize();
|
||||
|
||||
var timeout = DateTimeOffset.UtcNow.AddHours(1);
|
||||
var timeout = DateTimeOffset.UtcNow.AddHours(1);
|
||||
|
||||
var httpClient = new HttpClient();
|
||||
var httpClient = new HttpClient();
|
||||
|
||||
while (timeout > DateTimeOffset.UtcNow && !_cancelled)
|
||||
while (timeout > DateTimeOffset.UtcNow && !_cancelled)
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
var responseStream = await httpClient.GetStreamAsync(_uri);
|
||||
|
||||
if (responseStream == null)
|
||||
{
|
||||
var responseStream = await httpClient.GetStreamAsync(_uri);
|
||||
throw new IOException("No stream");
|
||||
}
|
||||
|
||||
if (responseStream == null)
|
||||
{
|
||||
throw new IOException("No stream");
|
||||
}
|
||||
|
||||
var speedLimit = Settings.Get.DownloadMaxSpeed;
|
||||
var speedLimit = Settings.Get.DownloadMaxSpeed;
|
||||
|
||||
await using var destinationStream = new ThrottledStream(responseStream, speedLimit * 1000L * 1000L);
|
||||
await using var destinationStream = new ThrottledStream(responseStream, speedLimit * 1000L * 1000L);
|
||||
|
||||
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 readSize = 1;
|
||||
var buffer = new Byte[BufferSize * 8];
|
||||
var readSize = 1;
|
||||
var buffer = new Byte[BufferSize * 8];
|
||||
|
||||
while (readSize > 0 && !_cancelled)
|
||||
while (readSize > 0 && !_cancelled)
|
||||
{
|
||||
// ReSharper disable once ConvertToUsingDeclaration
|
||||
using (var innerCts = new CancellationTokenSource(1000))
|
||||
{
|
||||
// ReSharper disable once ConvertToUsingDeclaration
|
||||
using (var innerCts = new CancellationTokenSource(1000))
|
||||
readSize = await destinationStream.ReadAsync(buffer.AsMemory(0, buffer.Length), innerCts.Token).ConfigureAwait(false);
|
||||
|
||||
await fileStream.WriteAsync(buffer.AsMemory(0, readSize), innerCts.Token);
|
||||
|
||||
_bytesDone = fileStream.Length;
|
||||
|
||||
if (DateTime.UtcNow > _nextUpdate)
|
||||
{
|
||||
readSize = await destinationStream.ReadAsync(buffer.AsMemory(0, buffer.Length), innerCts.Token).ConfigureAwait(false);
|
||||
_nextUpdate = DateTime.UtcNow.AddSeconds(1);
|
||||
|
||||
await fileStream.WriteAsync(buffer.AsMemory(0, readSize), innerCts.Token);
|
||||
timeout = DateTimeOffset.UtcNow.AddHours(1);
|
||||
|
||||
_bytesDone = fileStream.Length;
|
||||
DownloadProgress?.Invoke(this,
|
||||
new DownloadProgressEventArgs
|
||||
{
|
||||
Speed = destinationStream.Speed,
|
||||
BytesDone = _bytesDone,
|
||||
BytesTotal = _bytesTotal
|
||||
});
|
||||
|
||||
if (DateTime.UtcNow > _nextUpdate)
|
||||
if (Settings.Get.DownloadMaxSpeed != speedLimit)
|
||||
{
|
||||
_nextUpdate = DateTime.UtcNow.AddSeconds(1);
|
||||
|
||||
timeout = DateTimeOffset.UtcNow.AddHours(1);
|
||||
|
||||
DownloadProgress?.Invoke(this,
|
||||
new DownloadProgressEventArgs
|
||||
{
|
||||
Speed = destinationStream.Speed,
|
||||
BytesDone = _bytesDone,
|
||||
BytesTotal = _bytesTotal
|
||||
});
|
||||
|
||||
if (Settings.Get.DownloadMaxSpeed != speedLimit)
|
||||
{
|
||||
speedLimit = Settings.Get.DownloadMaxSpeed;
|
||||
destinationStream.BandwidthLimit = speedLimit * 1000L * 1000L;
|
||||
}
|
||||
speedLimit = Settings.Get.DownloadMaxSpeed;
|
||||
destinationStream.BandwidthLimit = speedLimit * 1000L * 1000L;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
catch (WebException)
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (_cancelled)
|
||||
catch (IOException)
|
||||
{
|
||||
throw new Exception("Download cancelled");
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
|
||||
if (timeout <= DateTimeOffset.UtcNow)
|
||||
catch (WebException)
|
||||
{
|
||||
throw new Exception($"Download timed out");
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
|
||||
DownloadComplete?.Invoke(this, new DownloadCompleteEventArgs());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DownloadComplete?.Invoke(this, new DownloadCompleteEventArgs
|
||||
{
|
||||
Error = ex.Message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Int64> GetContentSize()
|
||||
{
|
||||
var httpClient = new HttpClient
|
||||
{
|
||||
Timeout = TimeSpan.FromSeconds(5)
|
||||
};
|
||||
|
||||
var responseHeaders = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, _uri));
|
||||
|
||||
if (!responseHeaders.IsSuccessStatusCode)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return responseHeaders.Content.Headers.ContentLength ?? -1;
|
||||
}
|
||||
if (_cancelled)
|
||||
{
|
||||
throw new Exception("Download cancelled");
|
||||
}
|
||||
|
||||
public Task Pause()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
if (timeout <= DateTimeOffset.UtcNow)
|
||||
{
|
||||
throw new Exception($"Download timed out");
|
||||
}
|
||||
|
||||
public Task Resume()
|
||||
DownloadComplete.Invoke(this, new DownloadCompleteEventArgs());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
DownloadComplete.Invoke(this, new DownloadCompleteEventArgs
|
||||
{
|
||||
Error = ex.Message
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Int64> GetContentSize()
|
||||
{
|
||||
var httpClient = new HttpClient
|
||||
{
|
||||
Timeout = TimeSpan.FromSeconds(5)
|
||||
};
|
||||
|
||||
var responseHeaders = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Head, _uri));
|
||||
|
||||
if (!responseHeaders.IsSuccessStatusCode)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return responseHeaders.Content.Headers.ContentLength ?? -1;
|
||||
}
|
||||
|
||||
public Task Pause()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task Resume()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,98 +1,94 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using RdtClient.Data.Data;
|
||||
using RdtClient.Data.Data;
|
||||
using Download = RdtClient.Data.Models.Data.Download;
|
||||
|
||||
namespace RdtClient.Service.Services
|
||||
namespace RdtClient.Service.Services;
|
||||
|
||||
public class Downloads
|
||||
{
|
||||
public class Downloads
|
||||
private readonly DownloadData _downloadData;
|
||||
|
||||
public Downloads(DownloadData downloadData)
|
||||
{
|
||||
private readonly DownloadData _downloadData;
|
||||
|
||||
public Downloads(DownloadData downloadData)
|
||||
{
|
||||
_downloadData = downloadData;
|
||||
}
|
||||
|
||||
public async Task<List<Download>> GetForTorrent(Guid torrentId)
|
||||
{
|
||||
return await _downloadData.GetForTorrent(torrentId);
|
||||
}
|
||||
|
||||
public async Task<Download> GetById(Guid downloadId)
|
||||
{
|
||||
return await _downloadData.GetById(downloadId);
|
||||
}
|
||||
|
||||
public async Task<Download> Get(Guid torrentId, String path)
|
||||
{
|
||||
return await _downloadData.Get(torrentId, path);
|
||||
}
|
||||
|
||||
public async Task<Download> Add(Guid torrentId, String path)
|
||||
{
|
||||
return await _downloadData.Add(torrentId, path);
|
||||
}
|
||||
|
||||
public async Task UpdateUnrestrictedLink(Guid downloadId, String unrestrictedLink)
|
||||
{
|
||||
await _downloadData.UpdateUnrestrictedLink(downloadId, unrestrictedLink);
|
||||
}
|
||||
|
||||
public async Task UpdateDownloadStarted(Guid downloadId, DateTimeOffset? dateTime)
|
||||
{
|
||||
await _downloadData.UpdateDownloadStarted(downloadId, dateTime);
|
||||
}
|
||||
|
||||
public async Task UpdateDownloadFinished(Guid downloadId, DateTimeOffset? dateTime)
|
||||
{
|
||||
await _downloadData.UpdateDownloadFinished(downloadId, dateTime);
|
||||
}
|
||||
|
||||
public async Task UpdateUnpackingQueued(Guid downloadId, DateTimeOffset? dateTime)
|
||||
{
|
||||
await _downloadData.UpdateUnpackingQueued(downloadId, dateTime);
|
||||
}
|
||||
|
||||
public async Task UpdateUnpackingStarted(Guid downloadId, DateTimeOffset? dateTime)
|
||||
{
|
||||
await _downloadData.UpdateUnpackingStarted(downloadId, dateTime);
|
||||
}
|
||||
|
||||
public async Task UpdateUnpackingFinished(Guid downloadId, DateTimeOffset? dateTime)
|
||||
{
|
||||
await _downloadData.UpdateUnpackingFinished(downloadId, dateTime);
|
||||
}
|
||||
|
||||
public async Task UpdateCompleted(Guid downloadId, DateTimeOffset? dateTime)
|
||||
{
|
||||
await _downloadData.UpdateCompleted(downloadId, dateTime);
|
||||
}
|
||||
|
||||
public async Task UpdateError(Guid downloadId, String error)
|
||||
{
|
||||
await _downloadData.UpdateError(downloadId, error);
|
||||
}
|
||||
|
||||
public async Task UpdateRetryCount(Guid downloadId, Int32 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)
|
||||
{
|
||||
await _downloadData.DeleteForTorrent(torrentId);
|
||||
}
|
||||
|
||||
public async Task Reset(Guid downloadId)
|
||||
{
|
||||
await _downloadData.Reset(downloadId);
|
||||
}
|
||||
_downloadData = downloadData;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<Download>> GetForTorrent(Guid torrentId)
|
||||
{
|
||||
return await _downloadData.GetForTorrent(torrentId);
|
||||
}
|
||||
|
||||
public async Task<Download> GetById(Guid downloadId)
|
||||
{
|
||||
return await _downloadData.GetById(downloadId);
|
||||
}
|
||||
|
||||
public async Task<Download> Get(Guid torrentId, String path)
|
||||
{
|
||||
return await _downloadData.Get(torrentId, path);
|
||||
}
|
||||
|
||||
public async Task<Download> Add(Guid torrentId, String path)
|
||||
{
|
||||
return await _downloadData.Add(torrentId, path);
|
||||
}
|
||||
|
||||
public async Task UpdateUnrestrictedLink(Guid downloadId, String unrestrictedLink)
|
||||
{
|
||||
await _downloadData.UpdateUnrestrictedLink(downloadId, unrestrictedLink);
|
||||
}
|
||||
|
||||
public async Task UpdateDownloadStarted(Guid downloadId, DateTimeOffset? dateTime)
|
||||
{
|
||||
await _downloadData.UpdateDownloadStarted(downloadId, dateTime);
|
||||
}
|
||||
|
||||
public async Task UpdateDownloadFinished(Guid downloadId, DateTimeOffset? dateTime)
|
||||
{
|
||||
await _downloadData.UpdateDownloadFinished(downloadId, dateTime);
|
||||
}
|
||||
|
||||
public async Task UpdateUnpackingQueued(Guid downloadId, DateTimeOffset? dateTime)
|
||||
{
|
||||
await _downloadData.UpdateUnpackingQueued(downloadId, dateTime);
|
||||
}
|
||||
|
||||
public async Task UpdateUnpackingStarted(Guid downloadId, DateTimeOffset? dateTime)
|
||||
{
|
||||
await _downloadData.UpdateUnpackingStarted(downloadId, dateTime);
|
||||
}
|
||||
|
||||
public async Task UpdateUnpackingFinished(Guid downloadId, DateTimeOffset? dateTime)
|
||||
{
|
||||
await _downloadData.UpdateUnpackingFinished(downloadId, dateTime);
|
||||
}
|
||||
|
||||
public async Task UpdateCompleted(Guid downloadId, DateTimeOffset? dateTime)
|
||||
{
|
||||
await _downloadData.UpdateCompleted(downloadId, dateTime);
|
||||
}
|
||||
|
||||
public async Task UpdateError(Guid downloadId, String error)
|
||||
{
|
||||
await _downloadData.UpdateError(downloadId, error);
|
||||
}
|
||||
|
||||
public async Task UpdateRetryCount(Guid downloadId, Int32 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)
|
||||
{
|
||||
await _downloadData.DeleteForTorrent(torrentId);
|
||||
}
|
||||
|
||||
public async Task Reset(Guid downloadId)
|
||||
{
|
||||
await _downloadData.Reset(downloadId);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,27 +1,23 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Concurrent;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace RdtClient.Service.Services
|
||||
namespace RdtClient.Service.Services;
|
||||
|
||||
public class RdtHub : Hub
|
||||
{
|
||||
public class RdtHub : Hub
|
||||
private static readonly ConcurrentDictionary<String, String> Users = new();
|
||||
|
||||
public static Boolean HasConnections => Users.Any();
|
||||
|
||||
public override async Task OnConnectedAsync()
|
||||
{
|
||||
private static readonly ConcurrentDictionary<String, String> Users = new();
|
||||
|
||||
public static Boolean HasConnections => Users.Any();
|
||||
|
||||
public override async Task OnConnectedAsync()
|
||||
{
|
||||
Users.TryAdd(Context.ConnectionId, Context.ConnectionId);
|
||||
await base.OnConnectedAsync();
|
||||
}
|
||||
|
||||
public override async Task OnDisconnectedAsync(Exception exception)
|
||||
{
|
||||
Users.TryRemove(Context.ConnectionId, out _);
|
||||
await base.OnDisconnectedAsync(exception);
|
||||
}
|
||||
Users.TryAdd(Context.ConnectionId, Context.ConnectionId);
|
||||
await base.OnConnectedAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task OnDisconnectedAsync(Exception exception)
|
||||
{
|
||||
Users.TryRemove(Context.ConnectionId, out _);
|
||||
await base.OnDisconnectedAsync(exception);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +1,32 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace RdtClient.Service.Services
|
||||
namespace RdtClient.Service.Services;
|
||||
|
||||
public class RemoteService
|
||||
{
|
||||
public class RemoteService
|
||||
private readonly IHubContext<RdtHub> _hub;
|
||||
private readonly Torrents _torrents;
|
||||
|
||||
public RemoteService(IHubContext<RdtHub> hub, Torrents torrents)
|
||||
{
|
||||
private readonly IHubContext<RdtHub> _hub;
|
||||
private readonly Torrents _torrents;
|
||||
|
||||
public RemoteService(IHubContext<RdtHub> hub, Torrents torrents)
|
||||
{
|
||||
_hub = hub;
|
||||
_torrents = torrents;
|
||||
}
|
||||
|
||||
public async Task Update()
|
||||
{
|
||||
var torrents = await _torrents.Get();
|
||||
|
||||
// Prevent infinite recursion when serializing
|
||||
foreach (var file in torrents.SelectMany(torrent => torrent.Downloads))
|
||||
{
|
||||
file.Torrent = null;
|
||||
}
|
||||
|
||||
await _hub.Clients.All.SendCoreAsync("update",
|
||||
new Object[]
|
||||
{
|
||||
torrents
|
||||
});
|
||||
}
|
||||
_hub = hub;
|
||||
_torrents = torrents;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Update()
|
||||
{
|
||||
var torrents = await _torrents.Get();
|
||||
|
||||
// Prevent infinite recursion when serializing
|
||||
foreach (var file in torrents.SelectMany(torrent => torrent.Downloads))
|
||||
{
|
||||
file.Torrent = null;
|
||||
}
|
||||
|
||||
await _hub.Clients.All.SendCoreAsync("update",
|
||||
new Object[]
|
||||
{
|
||||
torrents
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,182 +1,180 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Diagnostics;
|
||||
using Aria2NET;
|
||||
using RdtClient.Data.Data;
|
||||
using RdtClient.Data.Models.Data;
|
||||
using RdtClient.Data.Models.Internal;
|
||||
using RdtClient.Service.Helpers;
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace RdtClient.Service.Services
|
||||
namespace RdtClient.Service.Services;
|
||||
|
||||
public class Settings
|
||||
{
|
||||
public class Settings
|
||||
public static readonly LoggingLevelSwitch LoggingLevelSwitch = new(LogEventLevel.Debug);
|
||||
|
||||
private readonly SettingData _settingData;
|
||||
|
||||
public Settings(SettingData settingData)
|
||||
{
|
||||
private readonly SettingData _settingData;
|
||||
_settingData = settingData;
|
||||
}
|
||||
|
||||
public Settings(SettingData settingData)
|
||||
public static DbSettings Get => SettingData.Get;
|
||||
|
||||
public async Task<IList<Setting>> GetAll()
|
||||
{
|
||||
return await _settingData.GetAll();
|
||||
}
|
||||
|
||||
public async Task Update(IList<Setting> settings)
|
||||
{
|
||||
await _settingData.Update(settings);
|
||||
}
|
||||
|
||||
public async Task UpdateString(String key, String value)
|
||||
{
|
||||
await _settingData.UpdateString(key, value);
|
||||
}
|
||||
|
||||
public async Task TestPath(String path)
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
_settingData = settingData;
|
||||
throw new Exception("Path is not set");
|
||||
}
|
||||
|
||||
public static DbSettings Get => SettingData.Get;
|
||||
path = path.TrimEnd('/').TrimEnd('\\');
|
||||
|
||||
public async Task<IList<Setting>> GetAll()
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
return await _settingData.GetAll();
|
||||
throw new Exception($"Path {path} does not exist");
|
||||
}
|
||||
|
||||
public async Task Update(IList<Setting> settings)
|
||||
{
|
||||
await _settingData.Update(settings);
|
||||
}
|
||||
var testFile = $"{path}/test.txt";
|
||||
|
||||
public async Task UpdateString(String key, String value)
|
||||
{
|
||||
await _settingData.UpdateString(key, value);
|
||||
}
|
||||
|
||||
public async Task TestPath(String path)
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
throw new Exception("Path is not set");
|
||||
}
|
||||
|
||||
path = path.TrimEnd('/').TrimEnd('\\');
|
||||
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
throw new Exception($"Path {path} does not exist");
|
||||
}
|
||||
|
||||
var testFile = $"{path}/test.txt";
|
||||
|
||||
await File.WriteAllTextAsync(testFile, "RealDebridClient Test File, you can remove this file.");
|
||||
await File.WriteAllTextAsync(testFile, "RealDebridClient Test File, you can remove this file.");
|
||||
|
||||
await FileHelper.Delete(testFile);
|
||||
}
|
||||
await FileHelper.Delete(testFile);
|
||||
}
|
||||
|
||||
public async Task<Double> TestDownloadSpeed(CancellationToken cancellationToken)
|
||||
public async Task<Double> TestDownloadSpeed(CancellationToken cancellationToken)
|
||||
{
|
||||
var downloadPath = Get.DownloadPath;
|
||||
|
||||
var testFilePath = Path.Combine(downloadPath, "testDefault.rar");
|
||||
|
||||
await FileHelper.Delete(testFilePath);
|
||||
|
||||
var download = new Download
|
||||
{
|
||||
var downloadPath = Get.DownloadPath;
|
||||
|
||||
var testFilePath = Path.Combine(downloadPath, "testDefault.rar");
|
||||
|
||||
await FileHelper.Delete(testFilePath);
|
||||
|
||||
var download = new Download
|
||||
Link = "https://34.download.real-debrid.com/speedtest/testDefault.rar",
|
||||
Torrent = new Torrent
|
||||
{
|
||||
Link = "https://34.download.real-debrid.com/speedtest/testDefault.rar",
|
||||
Torrent = new Torrent
|
||||
{
|
||||
RdName = ""
|
||||
}
|
||||
};
|
||||
RdName = ""
|
||||
}
|
||||
};
|
||||
|
||||
var downloadClient = new DownloadClient(download, download.Torrent, downloadPath);
|
||||
var downloadClient = new DownloadClient(download, download.Torrent, downloadPath);
|
||||
|
||||
await downloadClient.Start(Get);
|
||||
await downloadClient.Start(Get);
|
||||
|
||||
while (!downloadClient.Finished)
|
||||
{
|
||||
while (!downloadClient.Finished)
|
||||
{
|
||||
#pragma warning disable CA2016 // Forward the 'CancellationToken' parameter to methods that take one
|
||||
// ReSharper disable once MethodSupportsCancellation
|
||||
await Task.Delay(10);
|
||||
// ReSharper disable once MethodSupportsCancellation
|
||||
await Task.Delay(10);
|
||||
#pragma warning restore CA2016 // Forward the 'CancellationToken' parameter to methods that take one
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
await downloadClient.Cancel();
|
||||
}
|
||||
|
||||
if (downloadClient.BytesDone > 1024 * 1024 * 50)
|
||||
{
|
||||
await downloadClient.Cancel();
|
||||
|
||||
break;
|
||||
}
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
await downloadClient.Cancel();
|
||||
}
|
||||
|
||||
await FileHelper.Delete(testFilePath);
|
||||
if (downloadClient.BytesDone > 1024 * 1024 * 50)
|
||||
{
|
||||
await downloadClient.Cancel();
|
||||
|
||||
await Clean();
|
||||
|
||||
return downloadClient.Speed;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Double> TestWriteSpeed()
|
||||
await FileHelper.Delete(testFilePath);
|
||||
|
||||
await Clean();
|
||||
|
||||
return downloadClient.Speed;
|
||||
}
|
||||
|
||||
public async Task<Double> TestWriteSpeed()
|
||||
{
|
||||
var downloadPath = Get.DownloadPath;
|
||||
|
||||
var testFilePath = Path.Combine(downloadPath, "test.tmp");
|
||||
|
||||
await FileHelper.Delete(testFilePath);
|
||||
|
||||
const Int32 testFileSize = 64 * 1024 * 1024;
|
||||
|
||||
var watch = new Stopwatch();
|
||||
|
||||
watch.Start();
|
||||
|
||||
var rnd = new Random();
|
||||
|
||||
await using var fileStream = new FileStream(testFilePath, FileMode.Create, FileAccess.Write, FileShare.Write);
|
||||
|
||||
var buffer = new Byte[64 * 1024];
|
||||
|
||||
while (fileStream.Length < testFileSize)
|
||||
{
|
||||
var downloadPath = Get.DownloadPath;
|
||||
rnd.NextBytes(buffer);
|
||||
|
||||
var testFilePath = Path.Combine(downloadPath, "test.tmp");
|
||||
|
||||
await FileHelper.Delete(testFilePath);
|
||||
|
||||
const Int32 testFileSize = 64 * 1024 * 1024;
|
||||
|
||||
var watch = new Stopwatch();
|
||||
|
||||
watch.Start();
|
||||
|
||||
var rnd = new Random();
|
||||
|
||||
await using var fileStream = new FileStream(testFilePath, FileMode.Create, FileAccess.Write, FileShare.Write);
|
||||
|
||||
var buffer = new Byte[64 * 1024];
|
||||
|
||||
while (fileStream.Length < testFileSize)
|
||||
{
|
||||
rnd.NextBytes(buffer);
|
||||
|
||||
await fileStream.WriteAsync(buffer.AsMemory(0, buffer.Length));
|
||||
}
|
||||
await fileStream.WriteAsync(buffer.AsMemory(0, buffer.Length));
|
||||
}
|
||||
|
||||
watch.Stop();
|
||||
watch.Stop();
|
||||
|
||||
var writeSpeed = fileStream.Length / watch.Elapsed.TotalSeconds;
|
||||
var writeSpeed = fileStream.Length / watch.Elapsed.TotalSeconds;
|
||||
|
||||
fileStream.Close();
|
||||
fileStream.Close();
|
||||
|
||||
await FileHelper.Delete(testFilePath);
|
||||
await FileHelper.Delete(testFilePath);
|
||||
|
||||
return writeSpeed;
|
||||
}
|
||||
return writeSpeed;
|
||||
}
|
||||
|
||||
public async Task<VersionResult> GetAria2cVersion(String url, String secret)
|
||||
public async Task<VersionResult> GetAria2cVersion(String url, String secret)
|
||||
{
|
||||
var client = new Aria2NetClient(url, secret);
|
||||
|
||||
return await client.GetVersion();
|
||||
}
|
||||
|
||||
public async Task Clean()
|
||||
{
|
||||
try
|
||||
{
|
||||
var client = new Aria2NetClient(url, secret);
|
||||
var tempPath = Get.TempPath;
|
||||
|
||||
return await client.GetVersion();
|
||||
}
|
||||
|
||||
public async Task Clean()
|
||||
{
|
||||
try
|
||||
if (!String.IsNullOrWhiteSpace(tempPath))
|
||||
{
|
||||
var tempPath = Get.TempPath;
|
||||
var files = Directory.GetFiles(tempPath, "*.dsc", SearchOption.TopDirectoryOnly);
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(tempPath))
|
||||
foreach (var file in files)
|
||||
{
|
||||
var files = Directory.GetFiles(tempPath, "*.dsc", SearchOption.TopDirectoryOnly);
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
await FileHelper.Delete(file);
|
||||
}
|
||||
await FileHelper.Delete(file);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ResetCache()
|
||||
catch
|
||||
{
|
||||
await _settingData.ResetCache();
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ResetCache()
|
||||
{
|
||||
await _settingData.ResetCache();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +1,55 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using RdtClient.Data.Data;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace RdtClient.Service.Services
|
||||
namespace RdtClient.Service.Services;
|
||||
|
||||
public class Startup : IHostedService
|
||||
{
|
||||
public class Startup : IHostedService
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public Startup(IServiceProvider serviceProvider)
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public Startup(IServiceProvider serviceProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
|
||||
var settings = scope.ServiceProvider.GetRequiredService<Settings>();
|
||||
|
||||
await settings.ResetCache();
|
||||
|
||||
await settings.Clean();
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
}
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
|
||||
var dbContext = scope.ServiceProvider.GetRequiredService<DataContext>();
|
||||
await dbContext.Database.MigrateAsync(cancellationToken);
|
||||
await dbContext.Seed();
|
||||
|
||||
var logLevelSettingDb = await dbContext.Settings.FirstOrDefaultAsync(m => m.SettingId == "LogLevel", cancellationToken);
|
||||
|
||||
var logLevelSetting = "Warning";
|
||||
|
||||
if (logLevelSettingDb != null)
|
||||
{
|
||||
logLevelSetting = logLevelSettingDb.Value;
|
||||
}
|
||||
|
||||
if (!Enum.TryParse<LogEventLevel>(logLevelSetting, out var logLevel))
|
||||
{
|
||||
logLevel = LogEventLevel.Warning;
|
||||
}
|
||||
|
||||
Settings.LoggingLevelSwitch.MinimumLevel = logLevel;
|
||||
|
||||
var version = Assembly.GetEntryAssembly()?.GetName().Version;
|
||||
Log.Warning($"Starting host on version {version}");
|
||||
|
||||
var settings = scope.ServiceProvider.GetRequiredService<Settings>();
|
||||
|
||||
await settings.ResetCache();
|
||||
|
||||
await settings.Clean();
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
|
|
@ -1,49 +1,45 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace RdtClient.Service.Services
|
||||
namespace RdtClient.Service.Services;
|
||||
|
||||
public class TaskRunner : BackgroundService
|
||||
{
|
||||
public class TaskRunner : BackgroundService
|
||||
private readonly ILogger<TaskRunner> _logger;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public TaskRunner(ILogger<TaskRunner> logger, IServiceProvider serviceProvider)
|
||||
{
|
||||
private readonly ILogger<TaskRunner> _logger;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
_logger = logger;
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public TaskRunner(ILogger<TaskRunner> logger, IServiceProvider serviceProvider)
|
||||
{
|
||||
_logger = logger;
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken);
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken);
|
||||
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
var torrentRunner = scope.ServiceProvider.GetRequiredService<TorrentRunner>();
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
var torrentRunner = scope.ServiceProvider.GetRequiredService<TorrentRunner>();
|
||||
|
||||
_logger.LogInformation("TaskRunner started.");
|
||||
_logger.LogInformation("TaskRunner started.");
|
||||
|
||||
await torrentRunner.Initialize();
|
||||
await torrentRunner.Initialize();
|
||||
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
await torrentRunner.Tick();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Unexpected error occurred in TorrentDownloadManager.Tick");
|
||||
}
|
||||
|
||||
await Task.Delay(TimeSpan.FromSeconds(1), stoppingToken);
|
||||
await torrentRunner.Tick();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Unexpected error occurred in TorrentDownloadManager.Tick");
|
||||
}
|
||||
|
||||
_logger.LogInformation("TaskRunner stopped.");
|
||||
await Task.Delay(TimeSpan.FromSeconds(1), stoppingToken);
|
||||
}
|
||||
|
||||
_logger.LogInformation("TaskRunner stopped.");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,211 +1,205 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
namespace RdtClient.Service.Services;
|
||||
|
||||
namespace RdtClient.Service.Services
|
||||
/// <summary>
|
||||
/// Class for streaming data with throttling support.
|
||||
/// Taken from Downloader: https://github.com/bezzad/Downloader
|
||||
/// </summary>
|
||||
public class ThrottledStream : Stream
|
||||
{
|
||||
public Int64 Speed => (Int64)_bandwidth.AverageSpeed;
|
||||
|
||||
private Bandwidth _bandwidth;
|
||||
private Int64 _bandwidthLimit;
|
||||
private readonly Stream _baseStream;
|
||||
|
||||
/// <summary>
|
||||
/// Class for streaming data with throttling support.
|
||||
/// Taken from Downloader: https://github.com/bezzad/Downloader
|
||||
/// Initializes a new instance of the <see cref="T:ThrottledStream" /> class.
|
||||
/// </summary>
|
||||
public class ThrottledStream : Stream
|
||||
/// <param name="baseStream">The base stream.</param>
|
||||
/// <param name="bandwidthLimit">The maximum bytes per second that can be transferred through the base stream.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <see cref="baseStream" /> is a null reference.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when <see cref="BandwidthLimit" /> is a negative value.</exception>
|
||||
public ThrottledStream(Stream baseStream, Int64 bandwidthLimit)
|
||||
{
|
||||
public Int64 Speed => (Int64)_bandwidth.AverageSpeed;
|
||||
|
||||
private Bandwidth _bandwidth;
|
||||
private Int64 _bandwidthLimit;
|
||||
private readonly Stream _baseStream;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="T:ThrottledStream" /> class.
|
||||
/// </summary>
|
||||
/// <param name="baseStream">The base stream.</param>
|
||||
/// <param name="bandwidthLimit">The maximum bytes per second that can be transferred through the base stream.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when <see cref="baseStream" /> is a null reference.</exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">Thrown when <see cref="BandwidthLimit" /> is a negative value.</exception>
|
||||
public ThrottledStream(Stream baseStream, Int64 bandwidthLimit)
|
||||
if (bandwidthLimit < 0)
|
||||
{
|
||||
if (bandwidthLimit < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(bandwidthLimit),
|
||||
bandwidthLimit,
|
||||
"The maximum number of bytes per second can't be negative.");
|
||||
}
|
||||
|
||||
_baseStream = baseStream ?? throw new ArgumentNullException(nameof(baseStream));
|
||||
BandwidthLimit = bandwidthLimit;
|
||||
throw new ArgumentOutOfRangeException(nameof(bandwidthLimit),
|
||||
bandwidthLimit,
|
||||
"The maximum number of bytes per second can't be negative.");
|
||||
}
|
||||
|
||||
public static Int64 Infinite => Int64.MaxValue;
|
||||
_baseStream = baseStream ?? throw new ArgumentNullException(nameof(baseStream));
|
||||
BandwidthLimit = bandwidthLimit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bandwidth Limit (in B/s)
|
||||
/// </summary>
|
||||
/// <value>The maximum bytes per second.</value>
|
||||
public Int64 BandwidthLimit
|
||||
public static Int64 Infinite => Int64.MaxValue;
|
||||
|
||||
/// <summary>
|
||||
/// Bandwidth Limit (in B/s)
|
||||
/// </summary>
|
||||
/// <value>The maximum bytes per second.</value>
|
||||
public Int64 BandwidthLimit
|
||||
{
|
||||
get => _bandwidthLimit;
|
||||
set
|
||||
{
|
||||
get => _bandwidthLimit;
|
||||
set
|
||||
{
|
||||
_bandwidthLimit = value <= 0 ? Infinite : value;
|
||||
_bandwidth ??= new Bandwidth();
|
||||
_bandwidth.BandwidthLimit = _bandwidthLimit;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Boolean CanRead => _baseStream.CanRead;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Boolean CanSeek => _baseStream.CanSeek;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Boolean CanWrite => _baseStream.CanWrite;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Int64 Length => _baseStream.Length;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Int64 Position
|
||||
{
|
||||
get => _baseStream.Position;
|
||||
set => _baseStream.Position = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Flush()
|
||||
{
|
||||
_baseStream.Flush();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Int64 Seek(Int64 offset, SeekOrigin origin)
|
||||
{
|
||||
return _baseStream.Seek(offset, origin);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void SetLength(Int64 value)
|
||||
{
|
||||
_baseStream.SetLength(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Int32 Read(Byte[] buffer, Int32 offset, Int32 count)
|
||||
{
|
||||
Throttle(count).Wait();
|
||||
|
||||
return _baseStream.Read(buffer, offset, count);
|
||||
}
|
||||
|
||||
public override async Task<Int32> ReadAsync(Byte[] buffer,
|
||||
Int32 offset,
|
||||
Int32 count,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
await Throttle(count).ConfigureAwait(false);
|
||||
|
||||
return await _baseStream.ReadAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Write(Byte[] buffer, Int32 offset, Int32 count)
|
||||
{
|
||||
Throttle(count).Wait();
|
||||
_baseStream.Write(buffer, offset, count);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task WriteAsync(Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken)
|
||||
{
|
||||
await Throttle(count).ConfigureAwait(false);
|
||||
await _baseStream.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task Throttle(Int32 transmissionVolume)
|
||||
{
|
||||
// Make sure the buffer isn't empty.
|
||||
if (BandwidthLimit > 0 && transmissionVolume > 0)
|
||||
{
|
||||
// Calculate the time to sleep.
|
||||
_bandwidth.CalculateSpeed(transmissionVolume);
|
||||
|
||||
await Sleep(_bandwidth.PopSpeedRetrieveTime()).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task Sleep(Int32 time)
|
||||
{
|
||||
if (time > 0)
|
||||
{
|
||||
await Task.Delay(time).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override String ToString()
|
||||
{
|
||||
return _baseStream.ToString();
|
||||
_bandwidthLimit = value <= 0 ? Infinite : value;
|
||||
_bandwidth ??= new Bandwidth();
|
||||
_bandwidth.BandwidthLimit = _bandwidthLimit;
|
||||
}
|
||||
}
|
||||
|
||||
internal class Bandwidth
|
||||
/// <inheritdoc />
|
||||
public override Boolean CanRead => _baseStream.CanRead;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Boolean CanSeek => _baseStream.CanSeek;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Boolean CanWrite => _baseStream.CanWrite;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Int64 Length => _baseStream.Length;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Int64 Position
|
||||
{
|
||||
private const Double OneSecond = 1000; // millisecond
|
||||
private Int64 _count;
|
||||
private Int32 _lastSecondCheckpoint;
|
||||
private Int64 _lastTransferredBytesCount;
|
||||
private Int32 _speedRetrieveTime;
|
||||
get => _baseStream.Position;
|
||||
set => _baseStream.Position = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Flush()
|
||||
{
|
||||
_baseStream.Flush();
|
||||
}
|
||||
|
||||
public Bandwidth()
|
||||
/// <inheritdoc />
|
||||
public override Int64 Seek(Int64 offset, SeekOrigin origin)
|
||||
{
|
||||
return _baseStream.Seek(offset, origin);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void SetLength(Int64 value)
|
||||
{
|
||||
_baseStream.SetLength(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Int32 Read(Byte[] buffer, Int32 offset, Int32 count)
|
||||
{
|
||||
Throttle(count).Wait();
|
||||
|
||||
return _baseStream.Read(buffer, offset, count);
|
||||
}
|
||||
|
||||
public override async Task<Int32> ReadAsync(Byte[] buffer,
|
||||
Int32 offset,
|
||||
Int32 count,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
await Throttle(count).ConfigureAwait(false);
|
||||
|
||||
return await _baseStream.ReadAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Write(Byte[] buffer, Int32 offset, Int32 count)
|
||||
{
|
||||
Throttle(count).Wait();
|
||||
_baseStream.Write(buffer, offset, count);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task WriteAsync(Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken)
|
||||
{
|
||||
await Throttle(count).ConfigureAwait(false);
|
||||
await _baseStream.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task Throttle(Int32 transmissionVolume)
|
||||
{
|
||||
// Make sure the buffer isn't empty.
|
||||
if (BandwidthLimit > 0 && transmissionVolume > 0)
|
||||
{
|
||||
BandwidthLimit = Int64.MaxValue;
|
||||
Reset();
|
||||
// Calculate the time to sleep.
|
||||
_bandwidth.CalculateSpeed(transmissionVolume);
|
||||
|
||||
await Sleep(_bandwidth.PopSpeedRetrieveTime()).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public Double Speed { get; private set; }
|
||||
public Double AverageSpeed { get; private set; }
|
||||
public Int64 BandwidthLimit { get; set; }
|
||||
|
||||
public void CalculateSpeed(Int64 receivedBytesCount)
|
||||
private static async Task Sleep(Int32 time)
|
||||
{
|
||||
if (time > 0)
|
||||
{
|
||||
var elapsedTime = (Environment.TickCount - _lastSecondCheckpoint) + 1;
|
||||
receivedBytesCount = Interlocked.Add(ref _lastTransferredBytesCount, receivedBytesCount);
|
||||
var momentSpeed = (receivedBytesCount * OneSecond) / elapsedTime; // B/s
|
||||
|
||||
if (OneSecond < elapsedTime)
|
||||
{
|
||||
Speed = momentSpeed;
|
||||
AverageSpeed = ((AverageSpeed * _count) + Speed) / (_count + 1);
|
||||
_count++;
|
||||
SecondCheckpoint();
|
||||
}
|
||||
|
||||
if (momentSpeed >= BandwidthLimit)
|
||||
{
|
||||
var expectedTime = (receivedBytesCount * OneSecond) / BandwidthLimit;
|
||||
Interlocked.Add(ref _speedRetrieveTime, (Int32) expectedTime - elapsedTime);
|
||||
}
|
||||
await Task.Delay(time).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public Int32 PopSpeedRetrieveTime()
|
||||
{
|
||||
return Interlocked.Exchange(ref _speedRetrieveTime, 0);
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
SecondCheckpoint();
|
||||
_count = 0;
|
||||
Speed = 0;
|
||||
AverageSpeed = 0;
|
||||
}
|
||||
|
||||
private void SecondCheckpoint()
|
||||
{
|
||||
Interlocked.Exchange(ref _lastSecondCheckpoint, Environment.TickCount);
|
||||
Interlocked.Exchange(ref _lastTransferredBytesCount, 0);
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public override String ToString()
|
||||
{
|
||||
return _baseStream.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
internal class Bandwidth
|
||||
{
|
||||
private const Double OneSecond = 1000; // millisecond
|
||||
private Int64 _count;
|
||||
private Int32 _lastSecondCheckpoint;
|
||||
private Int64 _lastTransferredBytesCount;
|
||||
private Int32 _speedRetrieveTime;
|
||||
|
||||
public Bandwidth()
|
||||
{
|
||||
BandwidthLimit = Int64.MaxValue;
|
||||
Reset();
|
||||
}
|
||||
|
||||
public Double Speed { get; private set; }
|
||||
public Double AverageSpeed { get; private set; }
|
||||
public Int64 BandwidthLimit { get; set; }
|
||||
|
||||
public void CalculateSpeed(Int64 receivedBytesCount)
|
||||
{
|
||||
var elapsedTime = (Environment.TickCount - _lastSecondCheckpoint) + 1;
|
||||
receivedBytesCount = Interlocked.Add(ref _lastTransferredBytesCount, receivedBytesCount);
|
||||
var momentSpeed = (receivedBytesCount * OneSecond) / elapsedTime; // B/s
|
||||
|
||||
if (OneSecond < elapsedTime)
|
||||
{
|
||||
Speed = momentSpeed;
|
||||
AverageSpeed = ((AverageSpeed * _count) + Speed) / (_count + 1);
|
||||
_count++;
|
||||
SecondCheckpoint();
|
||||
}
|
||||
|
||||
if (momentSpeed >= BandwidthLimit)
|
||||
{
|
||||
var expectedTime = (receivedBytesCount * OneSecond) / BandwidthLimit;
|
||||
Interlocked.Add(ref _speedRetrieveTime, (Int32) expectedTime - elapsedTime);
|
||||
}
|
||||
}
|
||||
|
||||
public Int32 PopSpeedRetrieveTime()
|
||||
{
|
||||
return Interlocked.Exchange(ref _speedRetrieveTime, 0);
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
SecondCheckpoint();
|
||||
_count = 0;
|
||||
Speed = 0;
|
||||
AverageSpeed = 0;
|
||||
}
|
||||
|
||||
private void SecondCheckpoint()
|
||||
{
|
||||
Interlocked.Exchange(ref _lastSecondCheckpoint, Environment.TickCount);
|
||||
Interlocked.Exchange(ref _lastTransferredBytesCount, 0);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using AllDebridNET;
|
||||
using AllDebridNET;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using RDNET;
|
||||
|
|
@ -11,324 +6,324 @@ using RDNET.Exceptions;
|
|||
using RdtClient.Data.Enums;
|
||||
using RdtClient.Data.Models.TorrentClient;
|
||||
using RdtClient.Service.Helpers;
|
||||
using File = AllDebridNET.File;
|
||||
using Torrent = RdtClient.Data.Models.Data.Torrent;
|
||||
|
||||
namespace RdtClient.Service.Services.TorrentClients
|
||||
namespace RdtClient.Service.Services.TorrentClients;
|
||||
|
||||
public class AllDebridTorrentClient : ITorrentClient
|
||||
{
|
||||
public class AllDebridTorrentClient : ITorrentClient
|
||||
private readonly ILogger<AllDebridTorrentClient> _logger;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
|
||||
public AllDebridTorrentClient(ILogger<AllDebridTorrentClient> logger, IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
private readonly ILogger<AllDebridTorrentClient> _logger;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
|
||||
public AllDebridTorrentClient(ILogger<AllDebridTorrentClient> logger, IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
_logger = logger;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
}
|
||||
|
||||
private AllDebridNETClient GetClient()
|
||||
{
|
||||
var apiKey = Settings.Get.RealDebridApiKey;
|
||||
|
||||
if (String.IsNullOrWhiteSpace(apiKey))
|
||||
{
|
||||
throw new Exception("All-Debrid API Key not set in the settings");
|
||||
}
|
||||
|
||||
var httpClient = _httpClientFactory.CreateClient();
|
||||
httpClient.Timeout = TimeSpan.FromSeconds(10);
|
||||
|
||||
var allDebridNetClient = new AllDebridNETClient("RealDebridClient", apiKey);
|
||||
|
||||
return allDebridNetClient;
|
||||
}
|
||||
|
||||
private static TorrentClientTorrent Map(Magnet torrent)
|
||||
{
|
||||
return new TorrentClientTorrent
|
||||
{
|
||||
Id = torrent.Id.ToString(),
|
||||
Filename = torrent.Filename,
|
||||
OriginalFilename = torrent.Filename,
|
||||
Hash = torrent.Hash,
|
||||
Bytes = torrent.Size,
|
||||
OriginalBytes = torrent.Size,
|
||||
Host = null,
|
||||
Split = 0,
|
||||
Progress = (Int64) Math.Round(torrent.Downloaded * 100.0 / torrent.Size),
|
||||
Status = torrent.Status,
|
||||
StatusCode = torrent.StatusCode,
|
||||
Added = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(torrent.UploadDate),
|
||||
Files = (torrent.Links ?? new List<Link>()).Select((m, i) => new TorrentClientFile
|
||||
{
|
||||
Path = m.Filename,
|
||||
Bytes = m.Size,
|
||||
Id = i,
|
||||
Selected = true,
|
||||
}).ToList(),
|
||||
Links = (torrent.Links ?? new List<Link>()).Select(m => m.LinkUrl.ToString()).ToList(),
|
||||
Ended = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(torrent.CompletionDate),
|
||||
Speed = torrent.DownloadSpeed,
|
||||
Seeders = torrent.Seeders
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<IList<TorrentClientTorrent>> GetTorrents()
|
||||
{
|
||||
var results = await GetClient().Magnet.StatusAllAsync();
|
||||
return results.Select(Map).ToList();
|
||||
}
|
||||
|
||||
public async Task<TorrentClientUser> GetUser()
|
||||
{
|
||||
var user = await GetClient().User.GetAsync();
|
||||
|
||||
return new TorrentClientUser
|
||||
{
|
||||
Username = user.Username,
|
||||
Expiration = user.PremiumUntil > 0 ? new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(user.PremiumUntil) : null
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<String> AddMagnet(String magnetLink)
|
||||
{
|
||||
var result = await GetClient().Magnet.UploadMagnetAsync(magnetLink);
|
||||
|
||||
return result.Id.ToString();
|
||||
}
|
||||
|
||||
public async Task<String> AddFile(Byte[] bytes)
|
||||
{
|
||||
var result = await GetClient().Magnet.UploadFileAsync(bytes);
|
||||
|
||||
return result.Id.ToString();
|
||||
}
|
||||
|
||||
public async Task<IList<TorrentClientAvailableFile>> GetAvailableFiles(String hash)
|
||||
{
|
||||
var isAvailable = await GetClient().Magnet.InstantAvailabilityAsync(hash);
|
||||
|
||||
if (isAvailable)
|
||||
{
|
||||
return new List<TorrentClientAvailableFile>
|
||||
{
|
||||
new TorrentClientAvailableFile
|
||||
{
|
||||
Filename = "All files",
|
||||
Filesize = 0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return new List<TorrentClientAvailableFile>();
|
||||
}
|
||||
|
||||
public Task SelectFiles(Torrent torrent)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task<TorrentClientTorrent> GetInfo(String torrentId)
|
||||
{
|
||||
var result = await GetClient().Magnet.StatusAsync(torrentId);
|
||||
|
||||
return Map(result);
|
||||
}
|
||||
|
||||
public async Task Delete(String torrentId)
|
||||
{
|
||||
await GetClient().Magnet.DeleteAsync(torrentId);
|
||||
}
|
||||
|
||||
public async Task<String> Unrestrict(String link)
|
||||
{
|
||||
var result = await GetClient().Links.DownloadLinkAsync(link);
|
||||
|
||||
return result.Link;
|
||||
}
|
||||
|
||||
public async Task<Torrent> UpdateData(Torrent torrent, TorrentClientTorrent torrentClientTorrent)
|
||||
{
|
||||
try
|
||||
{
|
||||
torrentClientTorrent ??= await GetInfo(torrent.RdId);
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(torrentClientTorrent.Filename))
|
||||
{
|
||||
torrent.RdName = torrentClientTorrent.Filename;
|
||||
}
|
||||
|
||||
if (torrentClientTorrent.Bytes > 0)
|
||||
{
|
||||
torrent.RdSize = torrentClientTorrent.Bytes;
|
||||
}
|
||||
|
||||
if (torrentClientTorrent.Files != null)
|
||||
{
|
||||
torrent.RdFiles = JsonConvert.SerializeObject(torrentClientTorrent.Files);
|
||||
}
|
||||
|
||||
torrent.RdHost = torrentClientTorrent.Host;
|
||||
torrent.RdSplit = torrentClientTorrent.Split;
|
||||
torrent.RdProgress = torrentClientTorrent.Progress;
|
||||
torrent.RdAdded = torrentClientTorrent.Added;
|
||||
torrent.RdEnded = torrentClientTorrent.Ended;
|
||||
torrent.RdSpeed = torrentClientTorrent.Speed;
|
||||
torrent.RdSeeders = torrentClientTorrent.Seeders;
|
||||
torrent.RdStatusRaw = torrentClientTorrent.Status;
|
||||
|
||||
torrent.RdStatus = torrentClientTorrent.StatusCode switch
|
||||
{
|
||||
0 => TorrentStatus.Processing, // Processing In Queue.
|
||||
1 => TorrentStatus.Downloading, // Processing Downloading.
|
||||
2 => TorrentStatus.Downloading, // Processing Compressing / Moving.
|
||||
3 => TorrentStatus.Uploading, // Processing Uploading.
|
||||
4 => TorrentStatus.Finished, // Finished Ready.
|
||||
5 => TorrentStatus.Error, // Error Upload fail.
|
||||
6 => TorrentStatus.Error, // Error Internal error on unpacking.
|
||||
7 => TorrentStatus.Error, // Error Not downloaded in 20 min.
|
||||
8 => TorrentStatus.Error, // Error File too big.
|
||||
9 => TorrentStatus.Error, // Error Internal error.
|
||||
10 => TorrentStatus.Error, // Error Download took more than 72h.
|
||||
11 => TorrentStatus.Error, // Error Deleted on the hoster website
|
||||
_ => TorrentStatus.Error
|
||||
};
|
||||
}
|
||||
catch (AllDebridException ex)
|
||||
{
|
||||
if (ex.ErrorCode == "MAGNET_INVALID_ID")
|
||||
{
|
||||
torrent.RdStatusRaw = "deleted";
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
catch (RealDebridException ex)
|
||||
{
|
||||
if (ex.ErrorCode == 7)
|
||||
{
|
||||
torrent.RdStatusRaw = "deleted";
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return torrent;
|
||||
}
|
||||
|
||||
public async Task<IList<String>> GetDownloadLinks(Torrent torrent)
|
||||
{
|
||||
var magnet = await GetClient().Magnet.StatusAsync(torrent.RdId);
|
||||
|
||||
var links = magnet.Links;
|
||||
|
||||
Log($"Getting download links", torrent);
|
||||
|
||||
if (torrent.DownloadMinSize > 0)
|
||||
{
|
||||
var minFileSize = torrent.DownloadMinSize * 1024 * 1024;
|
||||
|
||||
Log($"Determining which files are over {minFileSize} bytes", torrent);
|
||||
|
||||
links = links.Where(m => m.Size > minFileSize)
|
||||
.ToList();
|
||||
|
||||
Log($"Found {links.Count} files that match the minimum file size criterea", torrent);
|
||||
}
|
||||
|
||||
if (links.Count == 0)
|
||||
{
|
||||
Log($"Filtered all files out! Downloading ALL files instead!", torrent);
|
||||
|
||||
links = magnet.Links;
|
||||
}
|
||||
|
||||
Log($"Selecting links:");
|
||||
|
||||
foreach (var link in links)
|
||||
{
|
||||
if (link.Files == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var fileList = GetFiles(link.Files, "");
|
||||
|
||||
Log($"{link.Filename} ({link.Size}b) {link.LinkUrl}, contains files:{Environment.NewLine}{String.Join(Environment.NewLine, fileList)}");
|
||||
}
|
||||
|
||||
Log("", torrent);
|
||||
|
||||
return links.Select(m => m.LinkUrl.ToString()).ToList();
|
||||
}
|
||||
|
||||
private static IEnumerable<String> GetFiles(IList<File> files, String parent)
|
||||
{
|
||||
var result = new List<String>();
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (!String.IsNullOrWhiteSpace(file.N))
|
||||
{
|
||||
result.Add($"{parent}/{file.N}");
|
||||
}
|
||||
|
||||
if (file.E != null && file.E.Value.PurpleEArray != null && file.E.Value.PurpleEArray.Count > 0)
|
||||
{
|
||||
result.AddRange(GetFiles(file.E.Value.PurpleEArray, file.N));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static IEnumerable<String> GetFiles(IList<FileE1> files, String parent)
|
||||
{
|
||||
var result = new List<String>();
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (!String.IsNullOrWhiteSpace(file.N))
|
||||
{
|
||||
result.Add($"{parent}/{file.N}");
|
||||
}
|
||||
|
||||
if (file.E != null && file.E.Count > 0)
|
||||
{
|
||||
result.AddRange(GetFiles(file.E, file.N));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static IEnumerable<String> GetFiles(IList<FileE2> files, String parent)
|
||||
{
|
||||
var result = new List<String>();
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (!String.IsNullOrWhiteSpace(file.N))
|
||||
{
|
||||
result.Add($"{parent}/{file.N}");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void Log(String message, Torrent torrent = null)
|
||||
{
|
||||
if (torrent != null)
|
||||
{
|
||||
message = $"{message} {torrent.ToLog()}";
|
||||
}
|
||||
|
||||
_logger.LogDebug(message);
|
||||
}
|
||||
_logger = logger;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
}
|
||||
}
|
||||
|
||||
private AllDebridNETClient GetClient()
|
||||
{
|
||||
var apiKey = Settings.Get.RealDebridApiKey;
|
||||
|
||||
if (String.IsNullOrWhiteSpace(apiKey))
|
||||
{
|
||||
throw new Exception("All-Debrid API Key not set in the settings");
|
||||
}
|
||||
|
||||
var httpClient = _httpClientFactory.CreateClient();
|
||||
httpClient.Timeout = TimeSpan.FromSeconds(10);
|
||||
|
||||
var allDebridNetClient = new AllDebridNETClient("RealDebridClient", apiKey);
|
||||
|
||||
return allDebridNetClient;
|
||||
}
|
||||
|
||||
private static TorrentClientTorrent Map(Magnet torrent)
|
||||
{
|
||||
return new TorrentClientTorrent
|
||||
{
|
||||
Id = torrent.Id.ToString(),
|
||||
Filename = torrent.Filename,
|
||||
OriginalFilename = torrent.Filename,
|
||||
Hash = torrent.Hash,
|
||||
Bytes = torrent.Size,
|
||||
OriginalBytes = torrent.Size,
|
||||
Host = null,
|
||||
Split = 0,
|
||||
Progress = (Int64) Math.Round(torrent.Downloaded * 100.0 / torrent.Size),
|
||||
Status = torrent.Status,
|
||||
StatusCode = torrent.StatusCode,
|
||||
Added = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(torrent.UploadDate),
|
||||
Files = (torrent.Links ?? new List<Link>()).Select((m, i) => new TorrentClientFile
|
||||
{
|
||||
Path = m.Filename,
|
||||
Bytes = m.Size,
|
||||
Id = i,
|
||||
Selected = true,
|
||||
}).ToList(),
|
||||
Links = (torrent.Links ?? new List<Link>()).Select(m => m.LinkUrl.ToString()).ToList(),
|
||||
Ended = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(torrent.CompletionDate),
|
||||
Speed = torrent.DownloadSpeed,
|
||||
Seeders = torrent.Seeders
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<IList<TorrentClientTorrent>> GetTorrents()
|
||||
{
|
||||
var results = await GetClient().Magnet.StatusAllAsync();
|
||||
return results.Select(Map).ToList();
|
||||
}
|
||||
|
||||
public async Task<TorrentClientUser> GetUser()
|
||||
{
|
||||
var user = await GetClient().User.GetAsync();
|
||||
|
||||
return new TorrentClientUser
|
||||
{
|
||||
Username = user.Username,
|
||||
Expiration = user.PremiumUntil > 0 ? new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(user.PremiumUntil) : null
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<String> AddMagnet(String magnetLink)
|
||||
{
|
||||
var result = await GetClient().Magnet.UploadMagnetAsync(magnetLink);
|
||||
|
||||
return result.Id.ToString();
|
||||
}
|
||||
|
||||
public async Task<String> AddFile(Byte[] bytes)
|
||||
{
|
||||
var result = await GetClient().Magnet.UploadFileAsync(bytes);
|
||||
|
||||
return result.Id.ToString();
|
||||
}
|
||||
|
||||
public async Task<IList<TorrentClientAvailableFile>> GetAvailableFiles(String hash)
|
||||
{
|
||||
var isAvailable = await GetClient().Magnet.InstantAvailabilityAsync(hash);
|
||||
|
||||
if (isAvailable)
|
||||
{
|
||||
return new List<TorrentClientAvailableFile>
|
||||
{
|
||||
new TorrentClientAvailableFile
|
||||
{
|
||||
Filename = "All files",
|
||||
Filesize = 0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return new List<TorrentClientAvailableFile>();
|
||||
}
|
||||
|
||||
public Task SelectFiles(Torrent torrent)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task<TorrentClientTorrent> GetInfo(String torrentId)
|
||||
{
|
||||
var result = await GetClient().Magnet.StatusAsync(torrentId);
|
||||
|
||||
return Map(result);
|
||||
}
|
||||
|
||||
public async Task Delete(String torrentId)
|
||||
{
|
||||
await GetClient().Magnet.DeleteAsync(torrentId);
|
||||
}
|
||||
|
||||
public async Task<String> Unrestrict(String link)
|
||||
{
|
||||
var result = await GetClient().Links.DownloadLinkAsync(link);
|
||||
|
||||
return result.Link;
|
||||
}
|
||||
|
||||
public async Task<Torrent> UpdateData(Torrent torrent, TorrentClientTorrent torrentClientTorrent)
|
||||
{
|
||||
try
|
||||
{
|
||||
torrentClientTorrent ??= await GetInfo(torrent.RdId);
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(torrentClientTorrent.Filename))
|
||||
{
|
||||
torrent.RdName = torrentClientTorrent.Filename;
|
||||
}
|
||||
|
||||
if (torrentClientTorrent.Bytes > 0)
|
||||
{
|
||||
torrent.RdSize = torrentClientTorrent.Bytes;
|
||||
}
|
||||
|
||||
if (torrentClientTorrent.Files != null)
|
||||
{
|
||||
torrent.RdFiles = JsonConvert.SerializeObject(torrentClientTorrent.Files);
|
||||
}
|
||||
|
||||
torrent.RdHost = torrentClientTorrent.Host;
|
||||
torrent.RdSplit = torrentClientTorrent.Split;
|
||||
torrent.RdProgress = torrentClientTorrent.Progress;
|
||||
torrent.RdAdded = torrentClientTorrent.Added;
|
||||
torrent.RdEnded = torrentClientTorrent.Ended;
|
||||
torrent.RdSpeed = torrentClientTorrent.Speed;
|
||||
torrent.RdSeeders = torrentClientTorrent.Seeders;
|
||||
torrent.RdStatusRaw = torrentClientTorrent.Status;
|
||||
|
||||
torrent.RdStatus = torrentClientTorrent.StatusCode switch
|
||||
{
|
||||
0 => TorrentStatus.Processing, // Processing In Queue.
|
||||
1 => TorrentStatus.Downloading, // Processing Downloading.
|
||||
2 => TorrentStatus.Downloading, // Processing Compressing / Moving.
|
||||
3 => TorrentStatus.Uploading, // Processing Uploading.
|
||||
4 => TorrentStatus.Finished, // Finished Ready.
|
||||
5 => TorrentStatus.Error, // Error Upload fail.
|
||||
6 => TorrentStatus.Error, // Error Internal error on unpacking.
|
||||
7 => TorrentStatus.Error, // Error Not downloaded in 20 min.
|
||||
8 => TorrentStatus.Error, // Error File too big.
|
||||
9 => TorrentStatus.Error, // Error Internal error.
|
||||
10 => TorrentStatus.Error, // Error Download took more than 72h.
|
||||
11 => TorrentStatus.Error, // Error Deleted on the hoster website
|
||||
_ => TorrentStatus.Error
|
||||
};
|
||||
}
|
||||
catch (AllDebridException ex)
|
||||
{
|
||||
if (ex.ErrorCode == "MAGNET_INVALID_ID")
|
||||
{
|
||||
torrent.RdStatusRaw = "deleted";
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
catch (RealDebridException ex)
|
||||
{
|
||||
if (ex.ErrorCode == 7)
|
||||
{
|
||||
torrent.RdStatusRaw = "deleted";
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return torrent;
|
||||
}
|
||||
|
||||
public async Task<IList<String>> GetDownloadLinks(Torrent torrent)
|
||||
{
|
||||
var magnet = await GetClient().Magnet.StatusAsync(torrent.RdId);
|
||||
|
||||
var links = magnet.Links;
|
||||
|
||||
Log($"Getting download links", torrent);
|
||||
|
||||
if (torrent.DownloadMinSize > 0)
|
||||
{
|
||||
var minFileSize = torrent.DownloadMinSize * 1024 * 1024;
|
||||
|
||||
Log($"Determining which files are over {minFileSize} bytes", torrent);
|
||||
|
||||
links = links.Where(m => m.Size > minFileSize)
|
||||
.ToList();
|
||||
|
||||
Log($"Found {links.Count} files that match the minimum file size criterea", torrent);
|
||||
}
|
||||
|
||||
if (links.Count == 0)
|
||||
{
|
||||
Log($"Filtered all files out! Downloading ALL files instead!", torrent);
|
||||
|
||||
links = magnet.Links;
|
||||
}
|
||||
|
||||
Log($"Selecting links:");
|
||||
|
||||
foreach (var link in links)
|
||||
{
|
||||
if (link.Files == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var fileList = GetFiles(link.Files, "");
|
||||
|
||||
Log($"{link.Filename} ({link.Size}b) {link.LinkUrl}, contains files:{Environment.NewLine}{String.Join(Environment.NewLine, fileList)}");
|
||||
}
|
||||
|
||||
Log("", torrent);
|
||||
|
||||
return links.Select(m => m.LinkUrl.ToString()).ToList();
|
||||
}
|
||||
|
||||
private static IEnumerable<String> GetFiles(IList<File> files, String parent)
|
||||
{
|
||||
var result = new List<String>();
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (!String.IsNullOrWhiteSpace(file.N))
|
||||
{
|
||||
result.Add($"{parent}/{file.N}");
|
||||
}
|
||||
|
||||
if (file.E != null && file.E.Value.PurpleEArray != null && file.E.Value.PurpleEArray.Count > 0)
|
||||
{
|
||||
result.AddRange(GetFiles(file.E.Value.PurpleEArray, file.N));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static IEnumerable<String> GetFiles(IList<FileE1> files, String parent)
|
||||
{
|
||||
var result = new List<String>();
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (!String.IsNullOrWhiteSpace(file.N))
|
||||
{
|
||||
result.Add($"{parent}/{file.N}");
|
||||
}
|
||||
|
||||
if (file.E != null && file.E.Count > 0)
|
||||
{
|
||||
result.AddRange(GetFiles(file.E, file.N));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static IEnumerable<String> GetFiles(IList<FileE2> files, String parent)
|
||||
{
|
||||
var result = new List<String>();
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (!String.IsNullOrWhiteSpace(file.N))
|
||||
{
|
||||
result.Add($"{parent}/{file.N}");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void Log(String message, Torrent torrent = null)
|
||||
{
|
||||
if (torrent != null)
|
||||
{
|
||||
message = $"{message} {torrent.ToLog()}";
|
||||
}
|
||||
|
||||
_logger.LogDebug(message);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +1,19 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using RdtClient.Data.Models.Data;
|
||||
using RdtClient.Data.Models.Data;
|
||||
using RdtClient.Data.Models.TorrentClient;
|
||||
|
||||
namespace RdtClient.Service.Services.TorrentClients
|
||||
namespace RdtClient.Service.Services.TorrentClients;
|
||||
|
||||
public interface ITorrentClient
|
||||
{
|
||||
public interface ITorrentClient
|
||||
{
|
||||
Task<IList<TorrentClientTorrent>> GetTorrents();
|
||||
Task<TorrentClientUser> GetUser();
|
||||
Task<String> AddMagnet(String magnetLink);
|
||||
Task<String> AddFile(Byte[] bytes);
|
||||
Task<IList<TorrentClientAvailableFile>> GetAvailableFiles(String hash);
|
||||
Task SelectFiles(Torrent torrent);
|
||||
Task<TorrentClientTorrent> GetInfo(String torrentId);
|
||||
Task Delete(String torrentId);
|
||||
Task<String> Unrestrict(String link);
|
||||
Task<Torrent> UpdateData(Torrent torrent, TorrentClientTorrent torrentClientTorrent);
|
||||
Task<IList<String>> GetDownloadLinks(Torrent torrent);
|
||||
}
|
||||
}
|
||||
Task<IList<TorrentClientTorrent>> GetTorrents();
|
||||
Task<TorrentClientUser> GetUser();
|
||||
Task<String> AddMagnet(String magnetLink);
|
||||
Task<String> AddFile(Byte[] bytes);
|
||||
Task<IList<TorrentClientAvailableFile>> GetAvailableFiles(String hash);
|
||||
Task SelectFiles(Torrent torrent);
|
||||
Task<TorrentClientTorrent> GetInfo(String torrentId);
|
||||
Task Delete(String torrentId);
|
||||
Task<String> Unrestrict(String link);
|
||||
Task<Torrent> UpdateData(Torrent torrent, TorrentClientTorrent torrentClientTorrent);
|
||||
Task<IList<String>> GetDownloadLinks(Torrent torrent);
|
||||
}
|
||||
|
|
@ -1,355 +1,349 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using RDNET;
|
||||
using RdtClient.Data.Enums;
|
||||
using RdtClient.Data.Models.TorrentClient;
|
||||
using RdtClient.Service.Helpers;
|
||||
|
||||
namespace RdtClient.Service.Services.TorrentClients
|
||||
namespace RdtClient.Service.Services.TorrentClients;
|
||||
|
||||
public class RealDebridTorrentClient : ITorrentClient
|
||||
{
|
||||
public class RealDebridTorrentClient : ITorrentClient
|
||||
private readonly ILogger<RealDebridTorrentClient> _logger;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private TimeSpan _offset;
|
||||
|
||||
public RealDebridTorrentClient(ILogger<RealDebridTorrentClient> logger, IHttpClientFactory httpClientFactory)
|
||||
{
|
||||
private readonly ILogger<RealDebridTorrentClient> _logger;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private TimeSpan _offset;
|
||||
_logger = logger;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
}
|
||||
|
||||
public RealDebridTorrentClient(ILogger<RealDebridTorrentClient> logger, IHttpClientFactory httpClientFactory)
|
||||
private RdNetClient GetClient()
|
||||
{
|
||||
var apiKey = Settings.Get.RealDebridApiKey;
|
||||
|
||||
if (String.IsNullOrWhiteSpace(apiKey))
|
||||
{
|
||||
_logger = logger;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
throw new Exception("Real-Debrid API Key not set in the settings");
|
||||
}
|
||||
|
||||
private RdNetClient GetClient()
|
||||
{
|
||||
var apiKey = Settings.Get.RealDebridApiKey;
|
||||
var httpClient = _httpClientFactory.CreateClient();
|
||||
httpClient.Timeout = TimeSpan.FromSeconds(10);
|
||||
|
||||
if (String.IsNullOrWhiteSpace(apiKey))
|
||||
var rdtNetClient = new RdNetClient(null, httpClient, 5);
|
||||
rdtNetClient.UseApiAuthentication(apiKey);
|
||||
|
||||
// Get the server time to fix up the timezones on results
|
||||
var serverTime = rdtNetClient.Api.GetIsoTimeAsync().Result;
|
||||
_offset = serverTime.Offset;
|
||||
|
||||
return rdtNetClient;
|
||||
}
|
||||
|
||||
private TorrentClientTorrent Map(Torrent torrent)
|
||||
{
|
||||
return new TorrentClientTorrent
|
||||
{
|
||||
Id = torrent.Id,
|
||||
Filename = torrent.Filename,
|
||||
OriginalFilename = torrent.OriginalFilename,
|
||||
Hash = torrent.Hash,
|
||||
Bytes = torrent.Bytes,
|
||||
OriginalBytes = torrent.OriginalBytes,
|
||||
Host = torrent.Host,
|
||||
Split = torrent.Split,
|
||||
Progress = torrent.Progress,
|
||||
Status = torrent.Status,
|
||||
Added = ChangeTimeZone(torrent.Added).Value,
|
||||
Files = (torrent.Files ?? new List<TorrentFile>()).Select(m => new TorrentClientFile
|
||||
{
|
||||
throw new Exception("Real-Debrid API Key not set in the settings");
|
||||
Path = m.Path,
|
||||
Bytes = m.Bytes,
|
||||
Id = m.Id,
|
||||
Selected = m.Selected
|
||||
}).ToList(),
|
||||
Links = torrent.Links,
|
||||
Ended = ChangeTimeZone(torrent.Ended),
|
||||
Speed = torrent.Speed,
|
||||
Seeders = torrent.Seeders,
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<IList<TorrentClientTorrent>> GetTorrents()
|
||||
{
|
||||
var page = 0;
|
||||
var results = new List<Torrent>();
|
||||
|
||||
while (true)
|
||||
{
|
||||
var pagedResults = await GetClient().Torrents.GetAsync(page, 100);
|
||||
|
||||
results.AddRange(pagedResults);
|
||||
|
||||
if (pagedResults.Count == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var httpClient = _httpClientFactory.CreateClient();
|
||||
httpClient.Timeout = TimeSpan.FromSeconds(10);
|
||||
|
||||
var rdtNetClient = new RdNetClient(null, httpClient, 5);
|
||||
rdtNetClient.UseApiAuthentication(apiKey);
|
||||
|
||||
// Get the server time to fix up the timezones on results
|
||||
var serverTime = rdtNetClient.Api.GetIsoTimeAsync().Result;
|
||||
_offset = serverTime.Offset;
|
||||
|
||||
return rdtNetClient;
|
||||
page += 100;
|
||||
}
|
||||
|
||||
private TorrentClientTorrent Map(Torrent torrent)
|
||||
{
|
||||
return new TorrentClientTorrent
|
||||
{
|
||||
Id = torrent.Id,
|
||||
Filename = torrent.Filename,
|
||||
OriginalFilename = torrent.OriginalFilename,
|
||||
Hash = torrent.Hash,
|
||||
Bytes = torrent.Bytes,
|
||||
OriginalBytes = torrent.OriginalBytes,
|
||||
Host = torrent.Host,
|
||||
Split = torrent.Split,
|
||||
Progress = torrent.Progress,
|
||||
Status = torrent.Status,
|
||||
Added = ChangeTimeZone(torrent.Added).Value,
|
||||
Files = (torrent.Files ?? new List<TorrentFile>()).Select(m => new TorrentClientFile
|
||||
{
|
||||
Path = m.Path,
|
||||
Bytes = m.Bytes,
|
||||
Id = m.Id,
|
||||
Selected = m.Selected
|
||||
}).ToList(),
|
||||
Links = torrent.Links,
|
||||
Ended = ChangeTimeZone(torrent.Ended),
|
||||
Speed = torrent.Speed,
|
||||
Seeders = torrent.Seeders,
|
||||
};
|
||||
}
|
||||
return results.Select(Map).ToList();
|
||||
}
|
||||
|
||||
public async Task<IList<TorrentClientTorrent>> GetTorrents()
|
||||
{
|
||||
var page = 0;
|
||||
var results = new List<Torrent>();
|
||||
|
||||
while (true)
|
||||
{
|
||||
var pagedResults = await GetClient().Torrents.GetAsync(page, 100);
|
||||
|
||||
results.AddRange(pagedResults);
|
||||
|
||||
if (pagedResults.Count == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
page += 100;
|
||||
}
|
||||
|
||||
return results.Select(Map).ToList();
|
||||
}
|
||||
|
||||
public async Task<TorrentClientUser> GetUser()
|
||||
{
|
||||
var user = await GetClient().User.GetAsync();
|
||||
public async Task<TorrentClientUser> GetUser()
|
||||
{
|
||||
var user = await GetClient().User.GetAsync();
|
||||
|
||||
return new TorrentClientUser
|
||||
{
|
||||
Username = user.Username,
|
||||
Expiration = user.Premium > 0 ? user.Expiration : null
|
||||
};
|
||||
return new TorrentClientUser
|
||||
{
|
||||
Username = user.Username,
|
||||
Expiration = user.Premium > 0 ? user.Expiration : null
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<String> AddMagnet(String magnetLink)
|
||||
{
|
||||
var result = await GetClient().Torrents.AddMagnetAsync(magnetLink);
|
||||
|
||||
return result.Id;
|
||||
}
|
||||
|
||||
public async Task<String> AddFile(Byte[] bytes)
|
||||
{
|
||||
var result = await GetClient().Torrents.AddFileAsync(bytes);
|
||||
|
||||
return result.Id;
|
||||
}
|
||||
|
||||
public async Task<IList<TorrentClientAvailableFile>> GetAvailableFiles(String hash)
|
||||
{
|
||||
var result = await GetClient().Torrents.GetAvailableFiles(hash);
|
||||
|
||||
var files = result.SelectMany(m => m.Value).SelectMany(m => m.Value).SelectMany(m => m.Values);
|
||||
|
||||
var groups = files.GroupBy(m => $"{m.Filename}-{m.Filesize}");
|
||||
|
||||
var torrentClientAvailableFiles = groups.Select(m => new TorrentClientAvailableFile
|
||||
{
|
||||
Filename = m.First().Filename,
|
||||
Filesize = m.First().Filesize
|
||||
} ).ToList();
|
||||
|
||||
return torrentClientAvailableFiles;
|
||||
}
|
||||
|
||||
public async Task SelectFiles(Data.Models.Data.Torrent torrent)
|
||||
{
|
||||
var files = torrent.Files;
|
||||
|
||||
Log("Seleting files", torrent);
|
||||
|
||||
if (torrent.DownloadAction == TorrentDownloadAction.DownloadAvailableFiles)
|
||||
{
|
||||
Log($"Determining which files are already available on RealDebrid", torrent);
|
||||
|
||||
var availableFiles = await GetAvailableFiles(torrent.Hash);
|
||||
|
||||
Log($"Found {files.Count}/{torrent.Files.Count} available files on RealDebrid", torrent);
|
||||
|
||||
files = torrent.Files.Where(m => availableFiles.Any(f => m.Path.EndsWith(f.Filename))).ToList();
|
||||
}
|
||||
else if (torrent.DownloadAction == TorrentDownloadAction.DownloadAll)
|
||||
{
|
||||
Log("Selecting all files", torrent);
|
||||
files = torrent.Files.ToList();
|
||||
}
|
||||
else if (torrent.DownloadAction == TorrentDownloadAction.DownloadManual)
|
||||
{
|
||||
Log("Selecting manual selected files", torrent);
|
||||
files = torrent.Files.Where(m => torrent.ManualFiles.Any(f => m.Path.EndsWith(f))).ToList();
|
||||
}
|
||||
|
||||
public async Task<String> AddMagnet(String magnetLink)
|
||||
{
|
||||
var result = await GetClient().Torrents.AddMagnetAsync(magnetLink);
|
||||
Log($"Selecting {files.Count}/{torrent.Files.Count} files", torrent);
|
||||
|
||||
return result.Id;
|
||||
if (torrent.DownloadAction != TorrentDownloadAction.DownloadManual && torrent.DownloadMinSize > 0)
|
||||
{
|
||||
var minFileSize = torrent.DownloadMinSize * 1024 * 1024;
|
||||
|
||||
Log($"Determining which files are over {minFileSize} bytes", torrent);
|
||||
|
||||
files = files.Where(m => m.Bytes > minFileSize)
|
||||
.ToList();
|
||||
|
||||
Log($"Found {files.Count} files that match the minimum file size criterea", torrent);
|
||||
}
|
||||
|
||||
public async Task<String> AddFile(Byte[] bytes)
|
||||
if (files.Count == 0)
|
||||
{
|
||||
var result = await GetClient().Torrents.AddFileAsync(bytes);
|
||||
Log($"Filtered all files out! Downloading ALL files instead!", torrent);
|
||||
|
||||
return result.Id;
|
||||
files = torrent.Files;
|
||||
}
|
||||
|
||||
public async Task<IList<TorrentClientAvailableFile>> GetAvailableFiles(String hash)
|
||||
var fileIds = files.Select(m => m.Id.ToString()).ToArray();
|
||||
|
||||
Log($"Selecting files:");
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
var result = await GetClient().Torrents.GetAvailableFiles(hash);
|
||||
|
||||
var files = result.SelectMany(m => m.Value).SelectMany(m => m.Value).SelectMany(m => m.Values);
|
||||
|
||||
var groups = files.GroupBy(m => $"{m.Filename}-{m.Filesize}");
|
||||
|
||||
var torrentClientAvailableFiles = groups.Select(m => new TorrentClientAvailableFile
|
||||
{
|
||||
Filename = m.First().Filename,
|
||||
Filesize = m.First().Filesize
|
||||
} ).ToList();
|
||||
|
||||
return torrentClientAvailableFiles;
|
||||
Log($"{file.Id}: {file.Path} ({file.Bytes}b)");
|
||||
}
|
||||
|
||||
public async Task SelectFiles(Data.Models.Data.Torrent torrent)
|
||||
Log("", torrent);
|
||||
|
||||
await GetClient().Torrents.SelectFilesAsync(torrent.RdId, fileIds.ToArray());
|
||||
}
|
||||
|
||||
public async Task<TorrentClientTorrent> GetInfo(String torrentId)
|
||||
{
|
||||
var result = await GetClient().Torrents.GetInfoAsync(torrentId);
|
||||
|
||||
return Map(result);
|
||||
}
|
||||
|
||||
public async Task Delete(String torrentId)
|
||||
{
|
||||
await GetClient().Torrents.DeleteAsync(torrentId);
|
||||
}
|
||||
|
||||
public async Task<String> Unrestrict(String link)
|
||||
{
|
||||
var result = await GetClient().Unrestrict.LinkAsync(link);
|
||||
|
||||
return result.Download;
|
||||
}
|
||||
|
||||
public async Task<Data.Models.Data.Torrent> UpdateData(Data.Models.Data.Torrent torrent, TorrentClientTorrent torrentClientTorrent)
|
||||
{
|
||||
try
|
||||
{
|
||||
var files = torrent.Files;
|
||||
|
||||
Log("Seleting files", torrent);
|
||||
|
||||
if (torrent.DownloadAction == TorrentDownloadAction.DownloadAvailableFiles)
|
||||
if (torrent == null)
|
||||
{
|
||||
Log($"Determining which files are already available on RealDebrid", torrent);
|
||||
|
||||
var availableFiles = await GetAvailableFiles(torrent.Hash);
|
||||
|
||||
Log($"Found {files.Count}/{torrent.Files.Count} available files on RealDebrid", torrent);
|
||||
|
||||
files = torrent.Files.Where(m => availableFiles.Any(f => m.Path.EndsWith(f.Filename))).ToList();
|
||||
}
|
||||
else if (torrent.DownloadAction == TorrentDownloadAction.DownloadAll)
|
||||
{
|
||||
Log("Selecting all files", torrent);
|
||||
files = torrent.Files.ToList();
|
||||
}
|
||||
else if (torrent.DownloadAction == TorrentDownloadAction.DownloadManual)
|
||||
{
|
||||
Log("Selecting manual selected files", torrent);
|
||||
files = torrent.Files.Where(m => torrent.ManualFiles.Any(f => m.Path.EndsWith(f))).ToList();
|
||||
return null;
|
||||
}
|
||||
|
||||
Log($"Selecting {files.Count}/{torrent.Files.Count} files", torrent);
|
||||
|
||||
if (torrent.DownloadAction != TorrentDownloadAction.DownloadManual && torrent.DownloadMinSize > 0)
|
||||
{
|
||||
var minFileSize = torrent.DownloadMinSize * 1024 * 1024;
|
||||
|
||||
Log($"Determining which files are over {minFileSize} bytes", torrent);
|
||||
|
||||
files = files.Where(m => m.Bytes > minFileSize)
|
||||
.ToList();
|
||||
|
||||
Log($"Found {files.Count} files that match the minimum file size criterea", torrent);
|
||||
}
|
||||
|
||||
if (files.Count == 0)
|
||||
{
|
||||
Log($"Filtered all files out! Downloading ALL files instead!", torrent);
|
||||
|
||||
files = torrent.Files;
|
||||
}
|
||||
|
||||
var fileIds = files.Select(m => m.Id.ToString()).ToArray();
|
||||
|
||||
Log($"Selecting files:");
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
Log($"{file.Id}: {file.Path} ({file.Bytes}b)");
|
||||
}
|
||||
|
||||
Log("", torrent);
|
||||
|
||||
await GetClient().Torrents.SelectFilesAsync(torrent.RdId, fileIds.ToArray());
|
||||
}
|
||||
|
||||
public async Task<TorrentClientTorrent> GetInfo(String torrentId)
|
||||
{
|
||||
var result = await GetClient().Torrents.GetInfoAsync(torrentId);
|
||||
|
||||
return Map(result);
|
||||
}
|
||||
|
||||
public async Task Delete(String torrentId)
|
||||
{
|
||||
await GetClient().Torrents.DeleteAsync(torrentId);
|
||||
}
|
||||
|
||||
public async Task<String> Unrestrict(String link)
|
||||
{
|
||||
var result = await GetClient().Unrestrict.LinkAsync(link);
|
||||
|
||||
return result.Download;
|
||||
}
|
||||
|
||||
public async Task<Data.Models.Data.Torrent> UpdateData(Data.Models.Data.Torrent torrent, TorrentClientTorrent torrentClientTorrent)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (torrent == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var rdTorrent = await GetInfo(torrent.RdId);
|
||||
|
||||
if (rdTorrent == null)
|
||||
{
|
||||
return torrent;
|
||||
}
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(rdTorrent.Filename))
|
||||
{
|
||||
torrent.RdName = rdTorrent.Filename;
|
||||
}
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(rdTorrent.OriginalFilename))
|
||||
{
|
||||
torrent.RdName = rdTorrent.OriginalFilename;
|
||||
}
|
||||
|
||||
if (rdTorrent.Bytes > 0)
|
||||
{
|
||||
torrent.RdSize = rdTorrent.Bytes;
|
||||
}
|
||||
else if (rdTorrent.OriginalBytes > 0)
|
||||
{
|
||||
torrent.RdSize = rdTorrent.OriginalBytes;
|
||||
}
|
||||
|
||||
if (rdTorrent.Files != null)
|
||||
{
|
||||
torrent.RdFiles = JsonConvert.SerializeObject(rdTorrent.Files);
|
||||
}
|
||||
|
||||
torrent.RdHost = rdTorrent.Host;
|
||||
torrent.RdSplit = rdTorrent.Split;
|
||||
torrent.RdProgress = rdTorrent.Progress;
|
||||
torrent.RdAdded = rdTorrent.Added;
|
||||
torrent.RdEnded = rdTorrent.Ended;
|
||||
torrent.RdSpeed = rdTorrent.Speed;
|
||||
torrent.RdSeeders = rdTorrent.Seeders;
|
||||
torrent.RdStatusRaw = rdTorrent.Status;
|
||||
|
||||
torrent.RdStatus = rdTorrent.Status switch
|
||||
{
|
||||
"magnet_error" => TorrentStatus.Error,
|
||||
"magnet_conversion" => TorrentStatus.Processing,
|
||||
"waiting_files_selection" => TorrentStatus.WaitingForFileSelection,
|
||||
"queued" => TorrentStatus.Downloading,
|
||||
"downloading" => TorrentStatus.Downloading,
|
||||
"downloaded" => TorrentStatus.Finished,
|
||||
"error" => TorrentStatus.Error,
|
||||
"virus" => TorrentStatus.Error,
|
||||
"compressing" => TorrentStatus.Downloading,
|
||||
"uploading" => TorrentStatus.Uploading,
|
||||
"dead" => TorrentStatus.Error,
|
||||
_ => TorrentStatus.Error
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex.Message == "Resource not found")
|
||||
{
|
||||
if (torrent != null)
|
||||
{
|
||||
torrent.RdStatusRaw = "deleted";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return torrent;
|
||||
}
|
||||
|
||||
public async Task<IList<String>> GetDownloadLinks(Data.Models.Data.Torrent torrent)
|
||||
{
|
||||
var rdTorrent = await GetInfo(torrent.RdId);
|
||||
|
||||
var downloadLinks = rdTorrent.Links.Where(m => !String.IsNullOrWhiteSpace(m)).ToList();
|
||||
|
||||
Log($"Found {downloadLinks.Count} links", torrent);
|
||||
|
||||
foreach (var link in downloadLinks)
|
||||
if (rdTorrent == null)
|
||||
{
|
||||
Log($"{link}", torrent);
|
||||
return torrent;
|
||||
}
|
||||
|
||||
// Check if all the links are set that have been selected
|
||||
if (torrent.Files.Count(m => m.Selected) == downloadLinks.Count)
|
||||
if (!String.IsNullOrWhiteSpace(rdTorrent.Filename))
|
||||
{
|
||||
return downloadLinks;
|
||||
torrent.RdName = rdTorrent.Filename;
|
||||
}
|
||||
|
||||
// Check if all all the links are set for manual selection
|
||||
if (torrent.ManualFiles.Count == downloadLinks.Count)
|
||||
if (!String.IsNullOrWhiteSpace(rdTorrent.OriginalFilename))
|
||||
{
|
||||
return downloadLinks;
|
||||
torrent.RdName = rdTorrent.OriginalFilename;
|
||||
}
|
||||
|
||||
// If there is only 1 link, delay for 1 minute to see if more links pop up.
|
||||
if (downloadLinks.Count == 1 && torrent.RdEnded.HasValue && DateTime.UtcNow > torrent.RdEnded.Value.ToUniversalTime().AddMinutes(1))
|
||||
if (rdTorrent.Bytes > 0)
|
||||
{
|
||||
return downloadLinks;
|
||||
torrent.RdSize = rdTorrent.Bytes;
|
||||
}
|
||||
|
||||
return null;
|
||||
else if (rdTorrent.OriginalBytes > 0)
|
||||
{
|
||||
torrent.RdSize = rdTorrent.OriginalBytes;
|
||||
}
|
||||
|
||||
if (rdTorrent.Files != null)
|
||||
{
|
||||
torrent.RdFiles = JsonConvert.SerializeObject(rdTorrent.Files);
|
||||
}
|
||||
|
||||
torrent.RdHost = rdTorrent.Host;
|
||||
torrent.RdSplit = rdTorrent.Split;
|
||||
torrent.RdProgress = rdTorrent.Progress;
|
||||
torrent.RdAdded = rdTorrent.Added;
|
||||
torrent.RdEnded = rdTorrent.Ended;
|
||||
torrent.RdSpeed = rdTorrent.Speed;
|
||||
torrent.RdSeeders = rdTorrent.Seeders;
|
||||
torrent.RdStatusRaw = rdTorrent.Status;
|
||||
|
||||
torrent.RdStatus = rdTorrent.Status switch
|
||||
{
|
||||
"magnet_error" => TorrentStatus.Error,
|
||||
"magnet_conversion" => TorrentStatus.Processing,
|
||||
"waiting_files_selection" => TorrentStatus.WaitingForFileSelection,
|
||||
"queued" => TorrentStatus.Downloading,
|
||||
"downloading" => TorrentStatus.Downloading,
|
||||
"downloaded" => TorrentStatus.Finished,
|
||||
"error" => TorrentStatus.Error,
|
||||
"virus" => TorrentStatus.Error,
|
||||
"compressing" => TorrentStatus.Downloading,
|
||||
"uploading" => TorrentStatus.Uploading,
|
||||
"dead" => TorrentStatus.Error,
|
||||
_ => TorrentStatus.Error
|
||||
};
|
||||
}
|
||||
|
||||
private DateTimeOffset? ChangeTimeZone(DateTimeOffset? dateTimeOffset)
|
||||
catch (Exception ex)
|
||||
{
|
||||
return dateTimeOffset?.Subtract(_offset).ToOffset(_offset);
|
||||
}
|
||||
|
||||
private void Log(String message, Data.Models.Data.Torrent torrent = null)
|
||||
{
|
||||
if (torrent != null)
|
||||
if (ex.Message == "Resource not found")
|
||||
{
|
||||
message = $"{message} {torrent.ToLog()}";
|
||||
if (torrent != null)
|
||||
{
|
||||
torrent.RdStatusRaw = "deleted";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
_logger.LogDebug(message);
|
||||
}
|
||||
|
||||
return torrent;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IList<String>> GetDownloadLinks(Data.Models.Data.Torrent torrent)
|
||||
{
|
||||
var rdTorrent = await GetInfo(torrent.RdId);
|
||||
|
||||
var downloadLinks = rdTorrent.Links.Where(m => !String.IsNullOrWhiteSpace(m)).ToList();
|
||||
|
||||
Log($"Found {downloadLinks.Count} links", torrent);
|
||||
|
||||
foreach (var link in downloadLinks)
|
||||
{
|
||||
Log($"{link}", torrent);
|
||||
}
|
||||
|
||||
// Check if all the links are set that have been selected
|
||||
if (torrent.Files.Count(m => m.Selected) == downloadLinks.Count)
|
||||
{
|
||||
return downloadLinks;
|
||||
}
|
||||
|
||||
// Check if all all the links are set for manual selection
|
||||
if (torrent.ManualFiles.Count == downloadLinks.Count)
|
||||
{
|
||||
return downloadLinks;
|
||||
}
|
||||
|
||||
// If there is only 1 link, delay for 1 minute to see if more links pop up.
|
||||
if (downloadLinks.Count == 1 && torrent.RdEnded.HasValue && DateTime.UtcNow > torrent.RdEnded.Value.ToUniversalTime().AddMinutes(1))
|
||||
{
|
||||
return downloadLinks;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private DateTimeOffset? ChangeTimeZone(DateTimeOffset? dateTimeOffset)
|
||||
{
|
||||
return dateTimeOffset?.Subtract(_offset).ToOffset(_offset);
|
||||
}
|
||||
|
||||
private void Log(String message, Data.Models.Data.Torrent torrent = null)
|
||||
{
|
||||
if (torrent != null)
|
||||
{
|
||||
message = $"{message} {torrent.ToLog()}";
|
||||
}
|
||||
|
||||
_logger.LogDebug(message);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,149 +1,143 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using RdtClient.Data.Models.Data;
|
||||
using RdtClient.Data.Models.Data;
|
||||
using RdtClient.Service.Helpers;
|
||||
using SharpCompress.Archives;
|
||||
using SharpCompress.Archives.Rar;
|
||||
using SharpCompress.Common;
|
||||
|
||||
namespace RdtClient.Service.Services
|
||||
namespace RdtClient.Service.Services;
|
||||
|
||||
public class UnpackClient
|
||||
{
|
||||
public class UnpackClient
|
||||
public Boolean Finished { get; private set; }
|
||||
|
||||
public String Error { get; private set; }
|
||||
|
||||
public Int64 BytesTotal { get; private set; }
|
||||
public Int64 BytesDone { get; private set; }
|
||||
|
||||
private readonly Download _download;
|
||||
private readonly String _destinationPath;
|
||||
private readonly Torrent _torrent;
|
||||
|
||||
private Boolean _cancelled;
|
||||
|
||||
private RarArchiveEntry _rarCurrentEntry;
|
||||
private Dictionary<String, Int64> _rarfileStatus;
|
||||
|
||||
public UnpackClient(Download download, String destinationPath)
|
||||
{
|
||||
public Boolean Finished { get; private set; }
|
||||
|
||||
public String Error { get; private set; }
|
||||
|
||||
public Int64 BytesTotal { get; private set; }
|
||||
public Int64 BytesDone { get; private set; }
|
||||
|
||||
private readonly Download _download;
|
||||
private readonly String _destinationPath;
|
||||
private readonly Torrent _torrent;
|
||||
_download = download;
|
||||
_destinationPath = destinationPath;
|
||||
_torrent = download.Torrent;
|
||||
}
|
||||
|
||||
private Boolean _cancelled;
|
||||
|
||||
private RarArchiveEntry _rarCurrentEntry;
|
||||
private Dictionary<String, Int64> _rarfileStatus;
|
||||
public void Start()
|
||||
{
|
||||
BytesDone = 0;
|
||||
BytesTotal = 0;
|
||||
|
||||
public UnpackClient(Download download, String destinationPath)
|
||||
try
|
||||
{
|
||||
_download = download;
|
||||
_destinationPath = destinationPath;
|
||||
_torrent = download.Torrent;
|
||||
}
|
||||
var filePath = DownloadHelper.GetDownloadPath(_destinationPath, _torrent, _download);
|
||||
|
||||
public void Start()
|
||||
{
|
||||
BytesDone = 0;
|
||||
BytesTotal = 0;
|
||||
|
||||
try
|
||||
if (filePath == null)
|
||||
{
|
||||
var filePath = DownloadHelper.GetDownloadPath(_destinationPath, _torrent, _download);
|
||||
throw new Exception("Invalid download path");
|
||||
}
|
||||
|
||||
if (filePath == null)
|
||||
Task.Run(async delegate
|
||||
{
|
||||
if (!_cancelled)
|
||||
{
|
||||
throw new Exception("Invalid download path");
|
||||
await Unpack(filePath);
|
||||
}
|
||||
|
||||
Task.Run(async delegate
|
||||
{
|
||||
if (!_cancelled)
|
||||
{
|
||||
await Unpack(filePath);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Error = $"An unexpected error occurred preparing download {_download.Link} for torrent {_torrent.RdName}: {ex.Message}";
|
||||
Finished = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
catch (Exception ex)
|
||||
{
|
||||
_cancelled = true;
|
||||
Error = $"An unexpected error occurred preparing download {_download.Link} for torrent {_torrent.RdName}: {ex.Message}";
|
||||
Finished = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Unpack(String filePath)
|
||||
public void Cancel()
|
||||
{
|
||||
_cancelled = true;
|
||||
}
|
||||
|
||||
private async Task Unpack(String filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await using (Stream stream = File.OpenRead(filePath))
|
||||
{
|
||||
using var archive = RarArchive.Open(stream);
|
||||
|
||||
BytesTotal = archive.TotalSize;
|
||||
|
||||
var entries = archive.Entries.Where(entry => !entry.IsDirectory)
|
||||
.ToList();
|
||||
|
||||
_rarfileStatus = entries.ToDictionary(entry => entry.Key, _ => 0L);
|
||||
_rarCurrentEntry = null;
|
||||
archive.CompressedBytesRead += ArchiveOnCompressedBytesRead;
|
||||
|
||||
var extractPath = _destinationPath;
|
||||
|
||||
if (!entries.Any(m => m.Key.StartsWith(_torrent.RdName + @"\")) && !entries.Any(m => m.Key.StartsWith(_torrent.RdName + @"/")))
|
||||
{
|
||||
extractPath = Path.Combine(_destinationPath, _torrent.RdName);
|
||||
}
|
||||
|
||||
if (entries.Any(m => m.Key.Contains(".r00")))
|
||||
{
|
||||
extractPath = Path.Combine(extractPath, "Temp");
|
||||
}
|
||||
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
if (_cancelled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_rarCurrentEntry = entry;
|
||||
|
||||
entry.WriteToDirectory(extractPath,
|
||||
new ExtractionOptions
|
||||
{
|
||||
ExtractFullPath = true,
|
||||
Overwrite = true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await FileHelper.Delete(filePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Error = $"An unexpected error occurred unpacking {_download.Link} for torrent {_torrent.RdName}: {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
Finished = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void ArchiveOnCompressedBytesRead(Object sender, CompressedBytesReadEventArgs e)
|
||||
{
|
||||
if (_rarCurrentEntry == null)
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_rarfileStatus[_rarCurrentEntry.Key] = e.CompressedBytesRead;
|
||||
await using (Stream stream = File.OpenRead(filePath))
|
||||
{
|
||||
using var archive = RarArchive.Open(stream);
|
||||
|
||||
BytesDone = _rarfileStatus.Sum(m => m.Value);
|
||||
BytesTotal = archive.TotalSize;
|
||||
|
||||
var entries = archive.Entries.Where(entry => !entry.IsDirectory)
|
||||
.ToList();
|
||||
|
||||
_rarfileStatus = entries.ToDictionary(entry => entry.Key, _ => 0L);
|
||||
_rarCurrentEntry = null;
|
||||
archive.CompressedBytesRead += ArchiveOnCompressedBytesRead;
|
||||
|
||||
var extractPath = _destinationPath;
|
||||
|
||||
if (!entries.Any(m => m.Key.StartsWith(_torrent.RdName + @"\")) && !entries.Any(m => m.Key.StartsWith(_torrent.RdName + @"/")))
|
||||
{
|
||||
extractPath = Path.Combine(_destinationPath, _torrent.RdName);
|
||||
}
|
||||
|
||||
if (entries.Any(m => m.Key.Contains(".r00")))
|
||||
{
|
||||
extractPath = Path.Combine(extractPath, "Temp");
|
||||
}
|
||||
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
if (_cancelled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_rarCurrentEntry = entry;
|
||||
|
||||
entry.WriteToDirectory(extractPath,
|
||||
new ExtractionOptions
|
||||
{
|
||||
ExtractFullPath = true,
|
||||
Overwrite = true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await FileHelper.Delete(filePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Error = $"An unexpected error occurred unpacking {_download.Link} for torrent {_torrent.RdName}: {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
Finished = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ArchiveOnCompressedBytesRead(Object sender, CompressedBytesReadEventArgs e)
|
||||
{
|
||||
if (_rarCurrentEntry == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_rarfileStatus[_rarCurrentEntry.Key] = e.CompressedBytesRead;
|
||||
|
||||
BytesDone = _rarfileStatus.Sum(m => m.Value);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,83 +1,76 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace RdtClient.Service.Services
|
||||
namespace RdtClient.Service.Services;
|
||||
|
||||
public class UpdateChecker : BackgroundService
|
||||
{
|
||||
public class UpdateChecker : BackgroundService
|
||||
public static String CurrentVersion { get; private set; }
|
||||
public static String LatestVersion { get; private set; }
|
||||
|
||||
private readonly ILogger<TaskRunner> _logger;
|
||||
|
||||
public UpdateChecker(ILogger<TaskRunner> logger)
|
||||
{
|
||||
public static String CurrentVersion { get; private set; }
|
||||
public static String LatestVersion { get; private set; }
|
||||
|
||||
private readonly ILogger<TaskRunner> _logger;
|
||||
|
||||
public UpdateChecker(ILogger<TaskRunner> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
var version = Assembly.GetEntryAssembly()?.GetName().Version?.ToString();
|
||||
|
||||
if (String.IsNullOrWhiteSpace(version))
|
||||
{
|
||||
CurrentVersion = "";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentVersion = $"v{version[..version.LastIndexOf(".", StringComparison.Ordinal)]}";
|
||||
|
||||
_logger.LogInformation("UpdateChecker started, currently on version {CurrentVersion}.", CurrentVersion);
|
||||
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
var httpClient = new HttpClient();
|
||||
httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("RdtClient", CurrentVersion));
|
||||
var response = await httpClient.GetStringAsync($"https://api.github.com/repos/rogerfar/rdt-client/tags?per_page=1", stoppingToken);
|
||||
|
||||
var gitHubReleases = JsonConvert.DeserializeObject<List<GitHubReleasesResponse>>(response);
|
||||
|
||||
if (gitHubReleases == null || gitHubReleases.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var latestRelease = gitHubReleases.First().Name;
|
||||
|
||||
if (latestRelease != CurrentVersion)
|
||||
{
|
||||
_logger.LogInformation("New version found on GitHub: {latestRelease}", latestRelease);
|
||||
}
|
||||
|
||||
LatestVersion = latestRelease;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Unexpected error occurred in TorrentDownloadManager.Tick");
|
||||
}
|
||||
|
||||
await Task.Delay(TimeSpan.FromHours(1), stoppingToken);
|
||||
}
|
||||
|
||||
_logger.LogInformation("UpdateChecker stopped.");
|
||||
}
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public class GitHubReleasesResponse
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
[JsonProperty("name")]
|
||||
public String Name { get; set; }
|
||||
var version = Assembly.GetEntryAssembly()?.GetName().Version?.ToString();
|
||||
|
||||
if (String.IsNullOrWhiteSpace(version))
|
||||
{
|
||||
CurrentVersion = "";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentVersion = $"v{version[..version.LastIndexOf(".", StringComparison.Ordinal)]}";
|
||||
|
||||
_logger.LogInformation("UpdateChecker started, currently on version {CurrentVersion}.", CurrentVersion);
|
||||
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
var httpClient = new HttpClient();
|
||||
httpClient.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("RdtClient", CurrentVersion));
|
||||
var response = await httpClient.GetStringAsync($"https://api.github.com/repos/rogerfar/rdt-client/tags?per_page=1", stoppingToken);
|
||||
|
||||
var gitHubReleases = JsonConvert.DeserializeObject<List<GitHubReleasesResponse>>(response);
|
||||
|
||||
if (gitHubReleases == null || gitHubReleases.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var latestRelease = gitHubReleases.First().Name;
|
||||
|
||||
if (latestRelease != CurrentVersion)
|
||||
{
|
||||
_logger.LogInformation("New version found on GitHub: {latestRelease}", latestRelease);
|
||||
}
|
||||
|
||||
LatestVersion = latestRelease;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Unexpected error occurred in TorrentDownloadManager.Tick");
|
||||
}
|
||||
|
||||
await Task.Delay(TimeSpan.FromHours(1), stoppingToken);
|
||||
}
|
||||
|
||||
_logger.LogInformation("UpdateChecker stopped.");
|
||||
}
|
||||
}
|
||||
|
||||
public class GitHubReleasesResponse
|
||||
{
|
||||
[JsonProperty("name")]
|
||||
public String Name { get; set; }
|
||||
}
|
||||
|
|
@ -1,120 +1,116 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using RdtClient.Service.Services;
|
||||
|
||||
namespace RdtClient.Web.Controllers
|
||||
namespace RdtClient.Web.Controllers;
|
||||
|
||||
[Route("Api/Authentication")]
|
||||
public class AuthController : Controller
|
||||
{
|
||||
[Route("Api/Authentication")]
|
||||
public class AuthController : Controller
|
||||
private readonly Authentication _authentication;
|
||||
|
||||
public AuthController(Authentication authentication)
|
||||
{
|
||||
private readonly Authentication _authentication;
|
||||
|
||||
public AuthController(Authentication authentication)
|
||||
{
|
||||
_authentication = authentication;
|
||||
}
|
||||
_authentication = authentication;
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[Route("IsLoggedIn")]
|
||||
[HttpGet]
|
||||
public async Task<ActionResult> IsLoggedIn()
|
||||
{
|
||||
if (User.Identity?.IsAuthenticated == false)
|
||||
{
|
||||
var user = await _authentication.GetUser();
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
return StatusCode(402, "Setup required");
|
||||
}
|
||||
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[Route("Create")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> Create([FromBody] AuthControllerLoginRequest request)
|
||||
{
|
||||
var user = await _authentication.GetUser();
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
return StatusCode(401);
|
||||
}
|
||||
|
||||
var registerResult = await _authentication.Register(request.UserName, request.Password);
|
||||
|
||||
if (!registerResult.Succeeded)
|
||||
{
|
||||
return BadRequest(registerResult.Errors.First().Description);
|
||||
}
|
||||
|
||||
await _authentication.Login(request.UserName, request.Password);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[Route("Login")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> Login([FromBody] AuthControllerLoginRequest request)
|
||||
[AllowAnonymous]
|
||||
[Route("IsLoggedIn")]
|
||||
[HttpGet]
|
||||
public async Task<ActionResult> IsLoggedIn()
|
||||
{
|
||||
if (User.Identity?.IsAuthenticated == false)
|
||||
{
|
||||
var user = await _authentication.GetUser();
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
return StatusCode(402);
|
||||
return StatusCode(402, "Setup required");
|
||||
}
|
||||
|
||||
var result = await _authentication.Login(request.UserName, request.Password);
|
||||
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
return BadRequest("Invalid credentials");
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Route("Logout")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> Logout()
|
||||
{
|
||||
await _authentication.Logout();
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Route("Update")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> Update([FromBody] AuthControllerUpdateRequest request)
|
||||
{
|
||||
var updateResult = await _authentication.Update(request.UserName, request.Password);
|
||||
|
||||
if (!updateResult.Succeeded)
|
||||
{
|
||||
return BadRequest(updateResult.Errors.First().Description);
|
||||
}
|
||||
|
||||
return Ok();
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[Route("Create")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> Create([FromBody] AuthControllerLoginRequest request)
|
||||
{
|
||||
var user = await _authentication.GetUser();
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
return StatusCode(401);
|
||||
}
|
||||
|
||||
var registerResult = await _authentication.Register(request.UserName, request.Password);
|
||||
|
||||
if (!registerResult.Succeeded)
|
||||
{
|
||||
return BadRequest(registerResult.Errors.First().Description);
|
||||
}
|
||||
|
||||
await _authentication.Login(request.UserName, request.Password);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
public class AuthControllerLoginRequest
|
||||
[AllowAnonymous]
|
||||
[Route("Login")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> Login([FromBody] AuthControllerLoginRequest request)
|
||||
{
|
||||
public String UserName { get; set; }
|
||||
public String Password { get; set; }
|
||||
}
|
||||
var user = await _authentication.GetUser();
|
||||
|
||||
public class AuthControllerUpdateRequest
|
||||
if (user == null)
|
||||
{
|
||||
return StatusCode(402);
|
||||
}
|
||||
|
||||
var result = await _authentication.Login(request.UserName, request.Password);
|
||||
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
return BadRequest("Invalid credentials");
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Route("Logout")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> Logout()
|
||||
{
|
||||
public String UserName { get; set; }
|
||||
public String Password { get; set; }
|
||||
await _authentication.Logout();
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Route("Update")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> Update([FromBody] AuthControllerUpdateRequest request)
|
||||
{
|
||||
var updateResult = await _authentication.Update(request.UserName, request.Password);
|
||||
|
||||
if (!updateResult.Succeeded)
|
||||
{
|
||||
return BadRequest(updateResult.Errors.First().Description);
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
|
||||
public class AuthControllerLoginRequest
|
||||
{
|
||||
public String UserName { get; set; }
|
||||
public String Password { get; set; }
|
||||
}
|
||||
|
||||
public class AuthControllerUpdateRequest
|
||||
{
|
||||
public String UserName { get; set; }
|
||||
public String Password { get; set; }
|
||||
}
|
||||
|
|
@ -1,500 +1,501 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using RdtClient.Data.Models.QBittorrent;
|
||||
using RdtClient.Data.Models.QBittorrent.QuickType;
|
||||
using RdtClient.Service.Services;
|
||||
|
||||
|
||||
namespace RdtClient.Web.Controllers
|
||||
namespace RdtClient.Web.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// This API behaves as a regular QBittorrent 4+ API
|
||||
/// Documentation is found here: https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/v2")]
|
||||
public class QBittorrentController : Controller
|
||||
{
|
||||
/// <summary>
|
||||
/// This API behaves as a regular QBittorrent 4+ API
|
||||
/// Documentation is found here: https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/v2")]
|
||||
public class QBittorrentController : Controller
|
||||
private readonly QBittorrent _qBittorrent;
|
||||
|
||||
public QBittorrentController(QBittorrent qBittorrent)
|
||||
{
|
||||
private readonly QBittorrent _qBittorrent;
|
||||
_qBittorrent = qBittorrent;
|
||||
}
|
||||
|
||||
public QBittorrentController(QBittorrent qBittorrent)
|
||||
[AllowAnonymous]
|
||||
[Route("auth/login")]
|
||||
[HttpGet]
|
||||
public async Task<ActionResult> AuthLogin([FromQuery] QBAuthLoginRequest request)
|
||||
{
|
||||
var result = await _qBittorrent.AuthLogin(request.UserName, request.Password);
|
||||
|
||||
if (result)
|
||||
{
|
||||
_qBittorrent = qBittorrent;
|
||||
return Ok("Ok.");
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[Route("auth/login")]
|
||||
[HttpGet]
|
||||
public async Task<ActionResult> AuthLogin([FromQuery] QBAuthLoginRequest request)
|
||||
{
|
||||
var result = await _qBittorrent.AuthLogin(request.UserName, request.Password);
|
||||
|
||||
if (result)
|
||||
{
|
||||
return Ok("Ok.");
|
||||
}
|
||||
|
||||
return Ok("Fails.");
|
||||
}
|
||||
return Ok("Fails.");
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[Route("auth/login")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> AuthLoginPost([FromForm] QBAuthLoginRequest request)
|
||||
{
|
||||
return await AuthLogin(request);
|
||||
}
|
||||
[AllowAnonymous]
|
||||
[Route("auth/login")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> AuthLoginPost([FromForm] QBAuthLoginRequest request)
|
||||
{
|
||||
return await AuthLogin(request);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("auth/logout")]
|
||||
[HttpGet]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> AuthLogout()
|
||||
{
|
||||
await _qBittorrent.AuthLogout();
|
||||
return Ok();
|
||||
}
|
||||
[Authorize]
|
||||
[Route("auth/logout")]
|
||||
[HttpGet]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> AuthLogout()
|
||||
{
|
||||
await _qBittorrent.AuthLogout();
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Route("app/version")]
|
||||
[HttpGet]
|
||||
[HttpPost]
|
||||
public ActionResult AppVersion()
|
||||
{
|
||||
return Ok("v4.3.2");
|
||||
}
|
||||
[Route("app/version")]
|
||||
[HttpGet]
|
||||
[HttpPost]
|
||||
public ActionResult AppVersion()
|
||||
{
|
||||
return Ok("v4.3.2");
|
||||
}
|
||||
|
||||
[Route("app/webapiVersion")]
|
||||
[HttpGet]
|
||||
[HttpPost]
|
||||
public ActionResult AppWebVersion()
|
||||
{
|
||||
return Ok("2.7");
|
||||
}
|
||||
[Route("app/webapiVersion")]
|
||||
[HttpGet]
|
||||
[HttpPost]
|
||||
public ActionResult AppWebVersion()
|
||||
{
|
||||
return Ok("2.7");
|
||||
}
|
||||
|
||||
[Route("app/buildInfo")]
|
||||
[HttpGet]
|
||||
[HttpPost]
|
||||
public ActionResult AppBuildInfo()
|
||||
[Route("app/buildInfo")]
|
||||
[HttpGet]
|
||||
[HttpPost]
|
||||
public ActionResult AppBuildInfo()
|
||||
{
|
||||
var result = new AppBuildInfo
|
||||
{
|
||||
var result = new AppBuildInfo
|
||||
{
|
||||
Bitness = 64,
|
||||
Boost = "1.75.0",
|
||||
Libtorrent = "1.2.11.0",
|
||||
Openssl = "1.1.1i",
|
||||
Qt = "5.15.2",
|
||||
Zlib = "1.2.11"
|
||||
};
|
||||
return Ok(result);
|
||||
}
|
||||
Bitness = 64,
|
||||
Boost = "1.75.0",
|
||||
Libtorrent = "1.2.11.0",
|
||||
Openssl = "1.1.1i",
|
||||
Qt = "5.15.2",
|
||||
Zlib = "1.2.11"
|
||||
};
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("app/shutdown")]
|
||||
[HttpGet]
|
||||
[HttpPost]
|
||||
public ActionResult AppShutdown()
|
||||
{
|
||||
return Ok();
|
||||
}
|
||||
[Authorize]
|
||||
[Route("app/shutdown")]
|
||||
[HttpGet]
|
||||
[HttpPost]
|
||||
public ActionResult AppShutdown()
|
||||
{
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[Route("app/preferences")]
|
||||
[HttpGet]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<AppPreferences>> AppPreferences()
|
||||
{
|
||||
var result = await _qBittorrent.AppPreferences();
|
||||
return Ok(result);
|
||||
}
|
||||
[AllowAnonymous]
|
||||
[Route("app/preferences")]
|
||||
[HttpGet]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<AppPreferences>> AppPreferences()
|
||||
{
|
||||
var result = await _qBittorrent.AppPreferences();
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("app/setPreferences")]
|
||||
[HttpGet]
|
||||
[HttpPost]
|
||||
public ActionResult AppSetPreferences()
|
||||
{
|
||||
return Ok();
|
||||
}
|
||||
[Authorize]
|
||||
[Route("app/setPreferences")]
|
||||
[HttpGet]
|
||||
[HttpPost]
|
||||
public ActionResult AppSetPreferences()
|
||||
{
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("app/defaultSavePath")]
|
||||
[HttpGet]
|
||||
[HttpPost]
|
||||
public ActionResult<AppPreferences> AppDefaultSavePath()
|
||||
{
|
||||
var result = _qBittorrent.AppDefaultSavePath();
|
||||
return Ok(result);
|
||||
}
|
||||
[Authorize]
|
||||
[Route("app/defaultSavePath")]
|
||||
[HttpGet]
|
||||
[HttpPost]
|
||||
public ActionResult<AppPreferences> AppDefaultSavePath()
|
||||
{
|
||||
var result = _qBittorrent.AppDefaultSavePath();
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/info")]
|
||||
[HttpGet]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<IList<TorrentInfo>>> TorrentsInfo([FromQuery] QBTorrentsHashRequest request)
|
||||
[Authorize]
|
||||
[Route("torrents/info")]
|
||||
[HttpGet]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<IList<TorrentInfo>>> TorrentsInfo([FromQuery] QBTorrentsInfoRequest request)
|
||||
{
|
||||
var results = await _qBittorrent.TorrentInfo();
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(request.Category))
|
||||
{
|
||||
var results = await _qBittorrent.TorrentInfo();
|
||||
IList<TorrentInfo> filteredResults = new List<TorrentInfo>();
|
||||
|
||||
if (request.Category == null) {
|
||||
filteredResults = results;
|
||||
} else if (request.Category == "") {
|
||||
filteredResults = results.Where(m => m.Category == null).ToList();
|
||||
} else {
|
||||
filteredResults = results.Where(m => m.Category == request.Category).ToList();
|
||||
}
|
||||
|
||||
return Ok(filteredResults);
|
||||
results = results.Where(m => m.Category == request.Category).ToList();
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/files")]
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<IList<TorrentFileItem>>> TorrentsFiles([FromQuery] QBTorrentsHashRequest request)
|
||||
{
|
||||
var result = await _qBittorrent.TorrentFileContents(request.Hash);
|
||||
return Ok(results);
|
||||
}
|
||||
|
||||
if (result == null)
|
||||
[Authorize]
|
||||
[Route("torrents/info")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<IList<TorrentInfo>>> TorrentsFilesPost([FromForm] QBTorrentsInfoRequest request)
|
||||
{
|
||||
return await TorrentsInfo(request);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/files")]
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<IList<TorrentFileItem>>> TorrentsFiles([FromQuery] QBTorrentsHashRequest request)
|
||||
{
|
||||
var result = await _qBittorrent.TorrentFileContents(request.Hash);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/files")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<IList<TorrentFileItem>>> TorrentsFilesPost([FromForm] QBTorrentsHashRequest request)
|
||||
{
|
||||
return await TorrentsFiles(request);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/properties")]
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<IList<TorrentInfo>>> TorrentsProperties([FromQuery] QBTorrentsHashRequest request)
|
||||
{
|
||||
var result = await _qBittorrent.TorrentProperties(request.Hash);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/properties")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<IList<TorrentInfo>>> TorrentsPropertiesPost([FromForm] QBTorrentsHashRequest request)
|
||||
{
|
||||
return await TorrentsProperties(request);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/pause")]
|
||||
[HttpGet]
|
||||
public async Task<ActionResult> TorrentsPause([FromQuery] QBTorrentsHashesRequest request)
|
||||
{
|
||||
var hashes = request.Hashes.Split("|");
|
||||
|
||||
foreach (var hash in hashes)
|
||||
{
|
||||
await _qBittorrent.TorrentPause(hash);
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/topPrio")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> TorrentsPausePost([FromForm] QBTorrentsHashesRequest request)
|
||||
{
|
||||
return await TorrentsPause(request);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/resume")]
|
||||
[HttpGet]
|
||||
public async Task<ActionResult> TorrentsResume([FromQuery] QBTorrentsHashesRequest request)
|
||||
{
|
||||
var hashes = request.Hashes.Split("|");
|
||||
|
||||
foreach (var hash in hashes)
|
||||
{
|
||||
await _qBittorrent.TorrentResume(hash);
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/topPrio")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> TorrentsResumePost([FromForm] QBTorrentsHashesRequest request)
|
||||
{
|
||||
return await TorrentsResume(request);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/setShareLimits")]
|
||||
[HttpGet]
|
||||
[HttpPost]
|
||||
public ActionResult TorrentsSetShareLimits()
|
||||
{
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/delete")]
|
||||
[HttpGet]
|
||||
public async Task<ActionResult> TorrentsDelete([FromQuery] QBTorrentsDeleteRequest request)
|
||||
{
|
||||
var hashes = request.Hashes.Split("|");
|
||||
|
||||
foreach (var hash in hashes)
|
||||
{
|
||||
await _qBittorrent.TorrentsDelete(hash, request.DeleteFiles);
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/delete")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> TorrentsDeletePost([FromForm] QBTorrentsDeleteRequest request)
|
||||
{
|
||||
return await TorrentsDelete(request);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/add")]
|
||||
[HttpGet]
|
||||
public async Task<ActionResult> TorrentsAdd([FromQuery] QBTorrentsAddRequest request)
|
||||
{
|
||||
var urls = request.Urls.Split("\n");
|
||||
|
||||
foreach (var url in urls)
|
||||
{
|
||||
if (url.StartsWith("magnet"))
|
||||
{
|
||||
return NotFound();
|
||||
await _qBittorrent.TorrentsAddMagnet(url.Trim(), request.Category, null);
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/files")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<IList<TorrentFileItem>>> TorrentsFilesPost([FromForm] QBTorrentsHashRequest request)
|
||||
{
|
||||
return await TorrentsFiles(request);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/properties")]
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<IList<TorrentInfo>>> TorrentsProperties([FromQuery] QBTorrentsHashRequest request)
|
||||
{
|
||||
var result = await _qBittorrent.TorrentProperties(request.Hash);
|
||||
|
||||
if (result == null)
|
||||
else if (url.StartsWith("http"))
|
||||
{
|
||||
return NotFound();
|
||||
var httpClient = new HttpClient();
|
||||
var result = await httpClient.GetByteArrayAsync(url);
|
||||
await _qBittorrent.TorrentsAddFile(result, request.Category, null);
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/properties")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<IList<TorrentInfo>>> TorrentsPropertiesPost([FromForm] QBTorrentsHashRequest request)
|
||||
{
|
||||
return await TorrentsProperties(request);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/pause")]
|
||||
[HttpGet]
|
||||
public async Task<ActionResult> TorrentsPause([FromQuery] QBTorrentsHashesRequest request)
|
||||
{
|
||||
var hashes = request.Hashes.Split("|");
|
||||
|
||||
foreach (var hash in hashes)
|
||||
else
|
||||
{
|
||||
await _qBittorrent.TorrentPause(hash);
|
||||
throw new Exception($"Invalid torrent link format {url}");
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/topPrio")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> TorrentsPausePost([FromForm] QBTorrentsHashesRequest request)
|
||||
{
|
||||
return await TorrentsPause(request);
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/resume")]
|
||||
[HttpGet]
|
||||
public async Task<ActionResult> TorrentsResume([FromQuery] QBTorrentsHashesRequest request)
|
||||
[Authorize]
|
||||
[Route("torrents/add")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> TorrentsAddPost([FromForm] QBTorrentsAddRequest request)
|
||||
{
|
||||
foreach (var file in Request.Form.Files)
|
||||
{
|
||||
var hashes = request.Hashes.Split("|");
|
||||
|
||||
foreach (var hash in hashes)
|
||||
if (file.Length > 0)
|
||||
{
|
||||
await _qBittorrent.TorrentResume(hash);
|
||||
await using var target = new MemoryStream();
|
||||
|
||||
await file.CopyToAsync(target);
|
||||
var fileBytes = target.ToArray();
|
||||
|
||||
await _qBittorrent.TorrentsAddFile(fileBytes, request.Category, request.Priority);
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/topPrio")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> TorrentsResumePost([FromForm] QBTorrentsHashesRequest request)
|
||||
{
|
||||
return await TorrentsResume(request);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/setShareLimits")]
|
||||
[HttpGet]
|
||||
[HttpPost]
|
||||
public ActionResult TorrentsSetShareLimits()
|
||||
{
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/delete")]
|
||||
[HttpGet]
|
||||
public async Task<ActionResult> TorrentsDelete([FromQuery] QBTorrentsDeleteRequest request)
|
||||
{
|
||||
var hashes = request.Hashes.Split("|");
|
||||
|
||||
foreach (var hash in hashes)
|
||||
{
|
||||
await _qBittorrent.TorrentsDelete(hash, request.DeleteFiles);
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/delete")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> TorrentsDeletePost([FromForm] QBTorrentsDeleteRequest request)
|
||||
{
|
||||
return await TorrentsDelete(request);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/add")]
|
||||
[HttpGet]
|
||||
public async Task<ActionResult> TorrentsAdd([FromQuery] QBTorrentsAddRequest request)
|
||||
{
|
||||
var urls = request.Urls.Split("\n");
|
||||
|
||||
foreach (var url in urls)
|
||||
{
|
||||
if (url.StartsWith("magnet"))
|
||||
{
|
||||
await _qBittorrent.TorrentsAddMagnet(url.Trim(), request.Category, null);
|
||||
}
|
||||
else if (url.StartsWith("http"))
|
||||
{
|
||||
var httpClient = new HttpClient();
|
||||
var result = await httpClient.GetByteArrayAsync(url);
|
||||
await _qBittorrent.TorrentsAddFile(result, request.Category, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Invalid torrent link format {url}");
|
||||
}
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/add")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> TorrentsAddPost([FromForm] QBTorrentsAddRequest request)
|
||||
{
|
||||
foreach (var file in Request.Form.Files)
|
||||
{
|
||||
if (file.Length > 0)
|
||||
{
|
||||
await using var target = new MemoryStream();
|
||||
|
||||
await file.CopyToAsync(target);
|
||||
var fileBytes = target.ToArray();
|
||||
|
||||
await _qBittorrent.TorrentsAddFile(fileBytes, request.Category, request.Priority);
|
||||
}
|
||||
}
|
||||
|
||||
if (request.Urls != null)
|
||||
{
|
||||
return await TorrentsAdd(request);
|
||||
}
|
||||
|
||||
return Ok();
|
||||
if (request.Urls != null)
|
||||
{
|
||||
return await TorrentsAdd(request);
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/setCategory")]
|
||||
[HttpGet]
|
||||
public async Task<ActionResult> TorrentsSetCategory([FromQuery] QBTorrentsSetCategoryRequest request)
|
||||
[Authorize]
|
||||
[Route("torrents/setCategory")]
|
||||
[HttpGet]
|
||||
public async Task<ActionResult> TorrentsSetCategory([FromQuery] QBTorrentsSetCategoryRequest request)
|
||||
{
|
||||
var hashes = request.Hashes.Split("|");
|
||||
|
||||
foreach (var hash in hashes)
|
||||
{
|
||||
var hashes = request.Hashes.Split("|");
|
||||
|
||||
foreach (var hash in hashes)
|
||||
{
|
||||
await _qBittorrent.TorrentsSetCategory(hash, request.Category);
|
||||
}
|
||||
|
||||
return Ok();
|
||||
await _qBittorrent.TorrentsSetCategory(hash, request.Category);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/setCategory")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> TorrentsSetCategoryPost([FromForm] QBTorrentsSetCategoryRequest request)
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/setCategory")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> TorrentsSetCategoryPost([FromForm] QBTorrentsSetCategoryRequest request)
|
||||
{
|
||||
return await TorrentsSetCategory(request);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/categories")]
|
||||
[HttpGet]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<IDictionary<String, TorrentCategory>>> TorrentsCategories()
|
||||
{
|
||||
var categories = await _qBittorrent.TorrentsCategories();
|
||||
|
||||
return Ok(categories);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/createCategory")]
|
||||
[HttpGet]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> TorrentsCreateCategory([FromForm] QBTorrentsCreateCategoryRequest request)
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(request.Category))
|
||||
{
|
||||
return await TorrentsSetCategory(request);
|
||||
return BadRequest("category name is empty");
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/categories")]
|
||||
[HttpGet]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<IDictionary<String, TorrentCategory>>> TorrentsCategories()
|
||||
{
|
||||
var categories = await _qBittorrent.TorrentsCategories();
|
||||
await _qBittorrent.CategoryCreate(request.Category.Trim());
|
||||
|
||||
return Ok(categories);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/createCategory")]
|
||||
[HttpGet]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> TorrentsCreateCategory([FromForm] QBTorrentsCreateCategoryRequest request)
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(request.Category))
|
||||
{
|
||||
return BadRequest("category name is empty");
|
||||
}
|
||||
|
||||
await _qBittorrent.CategoryCreate(request.Category.Trim());
|
||||
|
||||
return Ok();
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/removeCategories")]
|
||||
[HttpGet]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> TorrentsRemoveCategories([FromForm] QBTorrentsRemoveCategoryRequest request)
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(request.Categories))
|
||||
{
|
||||
return Ok();
|
||||
}
|
||||
|
||||
var categories = request.Categories.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
foreach (var category in categories)
|
||||
{
|
||||
await _qBittorrent.CategoryRemove(category.Trim());
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/setForcestart")]
|
||||
[HttpGet]
|
||||
[HttpPost]
|
||||
public ActionResult TorrentsSetForceStart()
|
||||
{
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/topPrio")]
|
||||
[HttpGet]
|
||||
public async Task<ActionResult> TorrentsTopPrio([FromQuery] QBTorrentsHashesRequest request)
|
||||
{
|
||||
var hashes = request.Hashes.Split("|");
|
||||
|
||||
foreach (var hash in hashes)
|
||||
{
|
||||
await _qBittorrent.TorrentsTopPrio(hash);
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/topPrio")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> TorrentsTopPrioPost([FromForm] QBTorrentsHashesRequest request)
|
||||
{
|
||||
return await TorrentsTopPrio(request);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("sync/maindata")]
|
||||
[HttpGet]
|
||||
public async Task<ActionResult> SyncMainData()
|
||||
{
|
||||
var result = await _qBittorrent.SyncMainData();
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("sync/maindata")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> SyncMainDataPost()
|
||||
{
|
||||
return await SyncMainData();
|
||||
}
|
||||
}
|
||||
|
||||
public class QBAuthLoginRequest
|
||||
[Authorize]
|
||||
[Route("torrents/removeCategories")]
|
||||
[HttpGet]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> TorrentsRemoveCategories([FromForm] QBTorrentsRemoveCategoryRequest request)
|
||||
{
|
||||
public String UserName { get; set; }
|
||||
public String Password { get; set; }
|
||||
if (String.IsNullOrWhiteSpace(request.Categories))
|
||||
{
|
||||
return Ok();
|
||||
}
|
||||
|
||||
var categories = request.Categories.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
foreach (var category in categories)
|
||||
{
|
||||
await _qBittorrent.CategoryRemove(category.Trim());
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/setForcestart")]
|
||||
[HttpGet]
|
||||
[HttpPost]
|
||||
public ActionResult TorrentsSetForceStart()
|
||||
{
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/topPrio")]
|
||||
[HttpGet]
|
||||
public async Task<ActionResult> TorrentsTopPrio([FromQuery] QBTorrentsHashesRequest request)
|
||||
{
|
||||
var hashes = request.Hashes.Split("|");
|
||||
|
||||
foreach (var hash in hashes)
|
||||
{
|
||||
await _qBittorrent.TorrentsTopPrio(hash);
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("torrents/topPrio")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> TorrentsTopPrioPost([FromForm] QBTorrentsHashesRequest request)
|
||||
{
|
||||
return await TorrentsTopPrio(request);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("sync/maindata")]
|
||||
[HttpGet]
|
||||
public async Task<ActionResult> SyncMainData()
|
||||
{
|
||||
var result = await _qBittorrent.SyncMainData();
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("sync/maindata")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> SyncMainDataPost()
|
||||
{
|
||||
return await SyncMainData();
|
||||
}
|
||||
}
|
||||
|
||||
public class QBAuthLoginRequest
|
||||
{
|
||||
public String UserName { get; set; }
|
||||
public String Password { get; set; }
|
||||
}
|
||||
|
||||
public class QBTorrentsInfoRequest
|
||||
{
|
||||
public String Category { get; set; }
|
||||
}
|
||||
|
||||
public class QBTorrentsHashRequest
|
||||
{
|
||||
public String Hash { get; set; }
|
||||
public String Category { get; set; }
|
||||
}
|
||||
public class QBTorrentsHashRequest
|
||||
{
|
||||
public String Hash { get; set; }
|
||||
public String Category { get; set; }
|
||||
}
|
||||
|
||||
public class QBTorrentsDeleteRequest
|
||||
{
|
||||
public String Hashes { get; set; }
|
||||
public Boolean DeleteFiles { get; set; }
|
||||
}
|
||||
public class QBTorrentsDeleteRequest
|
||||
{
|
||||
public String Hashes { get; set; }
|
||||
public Boolean DeleteFiles { get; set; }
|
||||
}
|
||||
|
||||
public class QBTorrentsAddRequest
|
||||
{
|
||||
public String Urls { get; set; }
|
||||
public String Category { get; set; }
|
||||
public Int32? Priority { get; set; }
|
||||
}
|
||||
public class QBTorrentsAddRequest
|
||||
{
|
||||
public String Urls { get; set; }
|
||||
public String Category { get; set; }
|
||||
public Int32? Priority { get; set; }
|
||||
}
|
||||
|
||||
public class QBTorrentsSetCategoryRequest
|
||||
{
|
||||
public String Hashes { get; set; }
|
||||
public String Category { get; set; }
|
||||
}
|
||||
public class QBTorrentsSetCategoryRequest
|
||||
{
|
||||
public String Hashes { get; set; }
|
||||
public String Category { get; set; }
|
||||
}
|
||||
|
||||
public class QBTorrentsCreateCategoryRequest
|
||||
{
|
||||
public String Category { get; set; }
|
||||
}
|
||||
public class QBTorrentsCreateCategoryRequest
|
||||
{
|
||||
public String Category { get; set; }
|
||||
}
|
||||
|
||||
public class QBTorrentsRemoveCategoryRequest
|
||||
{
|
||||
public String Categories { get; set; }
|
||||
}
|
||||
public class QBTorrentsRemoveCategoryRequest
|
||||
{
|
||||
public String Categories { get; set; }
|
||||
}
|
||||
|
||||
public class QBTorrentsHashesRequest
|
||||
{
|
||||
public String Hashes { get; set; }
|
||||
}
|
||||
public class QBTorrentsHashesRequest
|
||||
{
|
||||
public String Hashes { get; set; }
|
||||
}
|
||||
|
|
@ -1,111 +1,106 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using RdtClient.Data.Models.Data;
|
||||
using RdtClient.Data.Models.Internal;
|
||||
using RdtClient.Service.Services;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace RdtClient.Web.Controllers
|
||||
namespace RdtClient.Web.Controllers;
|
||||
|
||||
[Authorize]
|
||||
[Route("Api/Settings")]
|
||||
public class SettingsController : Controller
|
||||
{
|
||||
[Authorize]
|
||||
[Route("Api/Settings")]
|
||||
public class SettingsController : Controller
|
||||
private readonly Settings _settings;
|
||||
private readonly Torrents _torrents;
|
||||
|
||||
public SettingsController(Settings settings, Torrents torrents)
|
||||
{
|
||||
private readonly Settings _settings;
|
||||
private readonly Torrents _torrents;
|
||||
_settings = settings;
|
||||
_torrents = torrents;
|
||||
}
|
||||
|
||||
public SettingsController(Settings settings, Torrents torrents)
|
||||
{
|
||||
_settings = settings;
|
||||
_torrents = torrents;
|
||||
}
|
||||
[HttpGet]
|
||||
[Route("")]
|
||||
public async Task<ActionResult<IList<Setting>>> Get()
|
||||
{
|
||||
var result = await _settings.GetAll();
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("")]
|
||||
public async Task<ActionResult<IList<Setting>>> Get()
|
||||
{
|
||||
var result = await _settings.GetAll();
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpPut]
|
||||
[Route("")]
|
||||
public async Task<ActionResult> Update([FromBody] SettingsControllerUpdateRequest request)
|
||||
{
|
||||
await _settings.Update(request.Settings);
|
||||
[HttpPut]
|
||||
[Route("")]
|
||||
public async Task<ActionResult> Update([FromBody] SettingsControllerUpdateRequest request)
|
||||
{
|
||||
await _settings.Update(request.Settings);
|
||||
|
||||
if (!Enum.TryParse<LogEventLevel>(Settings.Get.LogLevel, out var logLevel))
|
||||
{
|
||||
logLevel = LogEventLevel.Information;
|
||||
}
|
||||
|
||||
Program.LoggingLevelSwitch.MinimumLevel = logLevel;
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("Profile")]
|
||||
public async Task<ActionResult<Profile>> Profile()
|
||||
if (!Enum.TryParse<LogEventLevel>(Settings.Get.LogLevel, out var logLevel))
|
||||
{
|
||||
var profile = await _torrents.GetProfile();
|
||||
return Ok(profile);
|
||||
logLevel = LogEventLevel.Information;
|
||||
}
|
||||
|
||||
Settings.LoggingLevelSwitch.MinimumLevel = logLevel;
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("Profile")]
|
||||
public async Task<ActionResult<Profile>> Profile()
|
||||
{
|
||||
var profile = await _torrents.GetProfile();
|
||||
return Ok(profile);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("TestPath")]
|
||||
public async Task<ActionResult> TestPath([FromBody] SettingsControllerTestPathRequest request)
|
||||
{
|
||||
await _settings.TestPath(request.Path);
|
||||
[HttpPost]
|
||||
[Route("TestPath")]
|
||||
public async Task<ActionResult> TestPath([FromBody] SettingsControllerTestPathRequest request)
|
||||
{
|
||||
await _settings.TestPath(request.Path);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("TestDownloadSpeed")]
|
||||
public async Task<ActionResult> TestDownloadSpeed(CancellationToken cancellationToken)
|
||||
{
|
||||
var downloadSpeed = await _settings.TestDownloadSpeed(cancellationToken);
|
||||
[HttpGet]
|
||||
[Route("TestDownloadSpeed")]
|
||||
public async Task<ActionResult> TestDownloadSpeed(CancellationToken cancellationToken)
|
||||
{
|
||||
var downloadSpeed = await _settings.TestDownloadSpeed(cancellationToken);
|
||||
|
||||
return Ok(downloadSpeed);
|
||||
}
|
||||
return Ok(downloadSpeed);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("TestWriteSpeed")]
|
||||
public async Task<ActionResult> TestWriteSpeed()
|
||||
{
|
||||
var writeSpeed = await _settings.TestWriteSpeed();
|
||||
[HttpGet]
|
||||
[Route("TestWriteSpeed")]
|
||||
public async Task<ActionResult> TestWriteSpeed()
|
||||
{
|
||||
var writeSpeed = await _settings.TestWriteSpeed();
|
||||
|
||||
return Ok(writeSpeed);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("TestAria2cConnection")]
|
||||
public async Task<ActionResult<String>> TestAria2cConnection([FromBody] SettingsControllerTestAria2cConnectionRequest request)
|
||||
{
|
||||
var version = await _settings.GetAria2cVersion(request.Url, request.Secret);
|
||||
|
||||
return Ok(version);
|
||||
}
|
||||
return Ok(writeSpeed);
|
||||
}
|
||||
|
||||
public class SettingsControllerUpdateRequest
|
||||
[HttpPost]
|
||||
[Route("TestAria2cConnection")]
|
||||
public async Task<ActionResult<String>> TestAria2cConnection([FromBody] SettingsControllerTestAria2cConnectionRequest request)
|
||||
{
|
||||
public IList<Setting> Settings { get; set; }
|
||||
}
|
||||
var version = await _settings.GetAria2cVersion(request.Url, request.Secret);
|
||||
|
||||
public class SettingsControllerTestPathRequest
|
||||
{
|
||||
public String Path { get; set; }
|
||||
return Ok(version);
|
||||
}
|
||||
}
|
||||
|
||||
public class SettingsControllerTestAria2cConnectionRequest
|
||||
{
|
||||
public String Url { get; set; }
|
||||
public String Secret { get; set; }
|
||||
}
|
||||
public class SettingsControllerUpdateRequest
|
||||
{
|
||||
public IList<Setting> Settings { get; set; }
|
||||
}
|
||||
|
||||
public class SettingsControllerTestPathRequest
|
||||
{
|
||||
public String Path { get; set; }
|
||||
}
|
||||
|
||||
public class SettingsControllerTestAria2cConnectionRequest
|
||||
{
|
||||
public String Url { get; set; }
|
||||
public String Secret { get; set; }
|
||||
}
|
||||
|
|
@ -1,220 +1,213 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using MonoTorrent;
|
||||
using RdtClient.Service.Helpers;
|
||||
using RdtClient.Service.Services;
|
||||
using Torrent = RdtClient.Data.Models.Data.Torrent;
|
||||
|
||||
namespace RdtClient.Web.Controllers
|
||||
{
|
||||
[Authorize]
|
||||
[Route("Api/Torrents")]
|
||||
public class TorrentsController : Controller
|
||||
{
|
||||
private readonly TorrentRunner _torrentRunner;
|
||||
private readonly Torrents _torrents;
|
||||
namespace RdtClient.Web.Controllers;
|
||||
|
||||
public TorrentsController(Torrents torrents, TorrentRunner torrentRunner)
|
||||
[Authorize]
|
||||
[Route("Api/Torrents")]
|
||||
public class TorrentsController : Controller
|
||||
{
|
||||
private readonly TorrentRunner _torrentRunner;
|
||||
private readonly Torrents _torrents;
|
||||
|
||||
public TorrentsController(Torrents torrents, TorrentRunner torrentRunner)
|
||||
{
|
||||
_torrents = torrents;
|
||||
_torrentRunner = torrentRunner;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("")]
|
||||
public async Task<ActionResult<IList<Torrent>>> GetAll()
|
||||
{
|
||||
var results = await _torrents.Get();
|
||||
|
||||
// Prevent infinite recursion when serializing
|
||||
foreach (var file in results.SelectMany(torrent => torrent.Downloads))
|
||||
{
|
||||
_torrents = torrents;
|
||||
_torrentRunner = torrentRunner;
|
||||
file.Torrent = null;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("")]
|
||||
public async Task<ActionResult<IList<Torrent>>> GetAll()
|
||||
{
|
||||
var results = await _torrents.Get();
|
||||
return Ok(results);
|
||||
}
|
||||
|
||||
// Prevent infinite recursion when serializing
|
||||
foreach (var file in results.SelectMany(torrent => torrent.Downloads))
|
||||
[HttpGet]
|
||||
[Route("Get/{torrentId:guid}")]
|
||||
public async Task<ActionResult<Torrent>> GetById(Guid torrentId)
|
||||
{
|
||||
var torrent = await _torrents.GetById(torrentId);
|
||||
|
||||
if (torrent?.Downloads != null)
|
||||
{
|
||||
foreach (var file in torrent.Downloads)
|
||||
{
|
||||
file.Torrent = null;
|
||||
}
|
||||
|
||||
return Ok(results);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("Get/{torrentId:guid}")]
|
||||
public async Task<ActionResult<Torrent>> GetById(Guid torrentId)
|
||||
return Ok(torrent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for debugging only. Force a tick.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
[Route("Tick")]
|
||||
public async Task<ActionResult> Tick()
|
||||
{
|
||||
await _torrentRunner.Tick();
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("UploadFile")]
|
||||
public async Task<ActionResult> UploadFile([FromForm] IFormFile file,
|
||||
[ModelBinder(BinderType = typeof(JsonModelBinder))]
|
||||
TorrentControllerUploadFileRequest formData)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
var torrent = await _torrents.GetById(torrentId);
|
||||
var errors = ModelState.Select(x => x.Value.Errors)
|
||||
.Where(y => y.Count > 0)
|
||||
.ToList();
|
||||
|
||||
if (torrent?.Downloads != null)
|
||||
{
|
||||
foreach (var file in torrent.Downloads)
|
||||
{
|
||||
file.Torrent = null;
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(torrent);
|
||||
return BadRequest(errors);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for debugging only. Force a tick.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
[Route("Tick")]
|
||||
public async Task<ActionResult> Tick()
|
||||
if (file == null || file.Length <= 0)
|
||||
{
|
||||
await _torrentRunner.Tick();
|
||||
|
||||
return Ok();
|
||||
throw new Exception("Invalid torrent file");
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("UploadFile")]
|
||||
public async Task<ActionResult> UploadFile([FromForm] IFormFile file,
|
||||
[ModelBinder(BinderType = typeof(JsonModelBinder))]
|
||||
TorrentControllerUploadFileRequest formData)
|
||||
var fileStream = file.OpenReadStream();
|
||||
|
||||
await using var memoryStream = new MemoryStream();
|
||||
|
||||
await fileStream.CopyToAsync(memoryStream);
|
||||
|
||||
var bytes = memoryStream.ToArray();
|
||||
|
||||
await _torrents.UploadFile(bytes, formData.Torrent);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("UploadMagnet")]
|
||||
public async Task<ActionResult> UploadMagnet([FromBody] TorrentControllerUploadMagnetRequest request)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
var errors = ModelState.Select(x => x.Value.Errors)
|
||||
.Where(y => y.Count > 0)
|
||||
.ToList();
|
||||
var errors = ModelState.Select(x => x.Value.Errors)
|
||||
.Where(y => y.Count > 0)
|
||||
.ToList();
|
||||
|
||||
return BadRequest(errors);
|
||||
}
|
||||
|
||||
if (file == null || file.Length <= 0)
|
||||
{
|
||||
throw new Exception("Invalid torrent file");
|
||||
}
|
||||
|
||||
var fileStream = file.OpenReadStream();
|
||||
|
||||
await using var memoryStream = new MemoryStream();
|
||||
|
||||
await fileStream.CopyToAsync(memoryStream);
|
||||
|
||||
var bytes = memoryStream.ToArray();
|
||||
|
||||
await _torrents.UploadFile(bytes, formData.Torrent);
|
||||
|
||||
return Ok();
|
||||
return BadRequest(errors);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("UploadMagnet")]
|
||||
public async Task<ActionResult> UploadMagnet([FromBody] TorrentControllerUploadMagnetRequest request)
|
||||
await _torrents.UploadMagnet(request.MagnetLink, request.Torrent);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("CheckFiles")]
|
||||
public async Task<ActionResult> CheckFiles([FromForm] IFormFile file)
|
||||
{
|
||||
if (file == null || file.Length <= 0)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
var errors = ModelState.Select(x => x.Value.Errors)
|
||||
.Where(y => y.Count > 0)
|
||||
.ToList();
|
||||
|
||||
return BadRequest(errors);
|
||||
}
|
||||
|
||||
await _torrents.UploadMagnet(request.MagnetLink, request.Torrent);
|
||||
|
||||
return Ok();
|
||||
throw new Exception("Invalid torrent file");
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("CheckFiles")]
|
||||
public async Task<ActionResult> CheckFiles([FromForm] IFormFile file)
|
||||
{
|
||||
if (file == null || file.Length <= 0)
|
||||
{
|
||||
throw new Exception("Invalid torrent file");
|
||||
}
|
||||
var fileStream = file.OpenReadStream();
|
||||
|
||||
var fileStream = file.OpenReadStream();
|
||||
await using var memoryStream = new MemoryStream();
|
||||
|
||||
await using var memoryStream = new MemoryStream();
|
||||
await fileStream.CopyToAsync(memoryStream);
|
||||
|
||||
await fileStream.CopyToAsync(memoryStream);
|
||||
var bytes = memoryStream.ToArray();
|
||||
|
||||
var bytes = memoryStream.ToArray();
|
||||
var torrent = await MonoTorrent.Torrent.LoadAsync(bytes);
|
||||
|
||||
var torrent = await MonoTorrent.Torrent.LoadAsync(bytes);
|
||||
var result = await _torrents.GetAvailableFiles(torrent.InfoHash.ToHex());
|
||||
|
||||
var result = await _torrents.GetAvailableFiles(torrent.InfoHash.ToHex());
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
[HttpPost]
|
||||
[Route("CheckFilesMagnet")]
|
||||
public async Task<ActionResult> CheckFilesMagnet([FromBody] TorrentControllerCheckFilesRequest request)
|
||||
{
|
||||
var magnet = MagnetLink.Parse(request.MagnetLink);
|
||||
|
||||
[HttpPost]
|
||||
[Route("CheckFilesMagnet")]
|
||||
public async Task<ActionResult> CheckFilesMagnet([FromBody] TorrentControllerCheckFilesRequest request)
|
||||
{
|
||||
var magnet = MagnetLink.Parse(request.MagnetLink);
|
||||
var result = await _torrents.GetAvailableFiles(magnet.InfoHash.ToHex());
|
||||
|
||||
var result = await _torrents.GetAvailableFiles(magnet.InfoHash.ToHex());
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
[HttpPost]
|
||||
[Route("Delete/{torrentId:guid}")]
|
||||
public async Task<ActionResult> Delete(Guid torrentId, [FromBody] TorrentControllerDeleteRequest request)
|
||||
{
|
||||
await _torrents.Delete(torrentId, request.DeleteData, request.DeleteRdTorrent, request.DeleteLocalFiles);
|
||||
|
||||
[HttpPost]
|
||||
[Route("Delete/{torrentId:guid}")]
|
||||
public async Task<ActionResult> Delete(Guid torrentId, [FromBody] TorrentControllerDeleteRequest request)
|
||||
{
|
||||
await _torrents.Delete(torrentId, request.DeleteData, request.DeleteRdTorrent, request.DeleteLocalFiles);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
[HttpPost]
|
||||
[Route("Retry/{torrentId:guid}")]
|
||||
public async Task<ActionResult> Retry(Guid torrentId)
|
||||
{
|
||||
await _torrents.UpdateRetry(torrentId, DateTimeOffset.UtcNow, 0);
|
||||
await _torrents.RetryTorrent(torrentId, 0);
|
||||
|
||||
[HttpPost]
|
||||
[Route("Retry/{torrentId:guid}")]
|
||||
public async Task<ActionResult> Retry(Guid torrentId)
|
||||
{
|
||||
await _torrents.UpdateRetry(torrentId, DateTimeOffset.UtcNow, 0);
|
||||
await _torrents.RetryTorrent(torrentId, 0);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
[HttpPost]
|
||||
[Route("RetryDownload/{downloadId:guid}")]
|
||||
public async Task<ActionResult> RetryDownload(Guid downloadId)
|
||||
{
|
||||
await _torrents.RetryDownload(downloadId);
|
||||
|
||||
[HttpPost]
|
||||
[Route("RetryDownload/{downloadId:guid}")]
|
||||
public async Task<ActionResult> RetryDownload(Guid downloadId)
|
||||
{
|
||||
await _torrents.RetryDownload(downloadId);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpPut]
|
||||
[Route("Update")]
|
||||
public async Task<ActionResult> Update([FromBody] Torrent torrent)
|
||||
{
|
||||
await _torrents.Update(torrent);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
|
||||
public class TorrentControllerUploadFileRequest
|
||||
[HttpPut]
|
||||
[Route("Update")]
|
||||
public async Task<ActionResult> Update([FromBody] Torrent torrent)
|
||||
{
|
||||
public Torrent Torrent { get; set; }
|
||||
}
|
||||
await _torrents.Update(torrent);
|
||||
|
||||
public class TorrentControllerUploadMagnetRequest
|
||||
{
|
||||
public String MagnetLink { get; set; }
|
||||
public Torrent Torrent { get; set; }
|
||||
}
|
||||
|
||||
public class TorrentControllerDeleteRequest
|
||||
{
|
||||
public Boolean DeleteData { get; set; }
|
||||
public Boolean DeleteRdTorrent { get; set; }
|
||||
public Boolean DeleteLocalFiles { get; set; }
|
||||
}
|
||||
|
||||
public class TorrentControllerCheckFilesRequest
|
||||
{
|
||||
public String MagnetLink { get; set; }
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
|
||||
public class TorrentControllerUploadFileRequest
|
||||
{
|
||||
public Torrent Torrent { get; set; }
|
||||
}
|
||||
|
||||
public class TorrentControllerUploadMagnetRequest
|
||||
{
|
||||
public String MagnetLink { get; set; }
|
||||
public Torrent Torrent { get; set; }
|
||||
}
|
||||
|
||||
public class TorrentControllerDeleteRequest
|
||||
{
|
||||
public Boolean DeleteData { get; set; }
|
||||
public Boolean DeleteRdTorrent { get; set; }
|
||||
public Boolean DeleteLocalFiles { get; set; }
|
||||
}
|
||||
|
||||
public class TorrentControllerCheckFilesRequest
|
||||
{
|
||||
public String MagnetLink { get; set; }
|
||||
}
|
||||
|
|
@ -1,126 +1,180 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System.Net;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using RdtClient.Data.Data;
|
||||
using RdtClient.Data.Models.Internal;
|
||||
using RdtClient.Service.Middleware;
|
||||
using RdtClient.Service.Services;
|
||||
using Serilog;
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
using Serilog.Exceptions;
|
||||
|
||||
namespace RdtClient.Web
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Bind the AppSettings from the appsettings.json files.
|
||||
builder.Configuration.AddJsonFile("appsettings.json", false, false);
|
||||
builder.Configuration.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", true, false);
|
||||
|
||||
// Bind AppSettings
|
||||
var appSettings = new AppSettings();
|
||||
builder.Configuration.Bind(appSettings);
|
||||
builder.Services.AddSingleton(appSettings);
|
||||
|
||||
// Configure URLs
|
||||
if (appSettings.Port <= 0)
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static readonly LoggingLevelSwitch LoggingLevelSwitch = new(LogEventLevel.Debug);
|
||||
|
||||
public static async Task Main(String[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
var host = CreateHostBuilder(args).Build();
|
||||
|
||||
// Perform migrations
|
||||
using (var scope = host.Services.CreateScope())
|
||||
{
|
||||
var dbContext = scope.ServiceProvider.GetRequiredService<DataContext>();
|
||||
await dbContext.Database.MigrateAsync();
|
||||
await dbContext.Seed();
|
||||
|
||||
var logLevelSettingDb = await dbContext.Settings.FirstOrDefaultAsync(m => m.SettingId == "LogLevel");
|
||||
|
||||
var logLevelSetting = "Warning";
|
||||
|
||||
if (logLevelSettingDb != null)
|
||||
{
|
||||
logLevelSetting = logLevelSettingDb.Value;
|
||||
}
|
||||
|
||||
if (!Enum.TryParse<LogEventLevel>(logLevelSetting, out var logLevel))
|
||||
{
|
||||
logLevel = LogEventLevel.Warning;
|
||||
}
|
||||
|
||||
LoggingLevelSwitch.MinimumLevel = logLevel;
|
||||
}
|
||||
|
||||
var version = Assembly.GetEntryAssembly()?.GetName().Version;
|
||||
Log.Warning($"Starting host on version {version}");
|
||||
|
||||
await host.RunAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Fatal(ex, "Host terminated unexpectedly");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Log.CloseAndFlush();
|
||||
}
|
||||
}
|
||||
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
public static IHostBuilder CreateHostBuilder(String[] args)
|
||||
{
|
||||
var configuration = new ConfigurationBuilder()
|
||||
#if DEBUG
|
||||
.AddJsonFile("appsettings.Development.json", true, false)
|
||||
#else
|
||||
.AddJsonFile("appsettings.json", true, false)
|
||||
#endif
|
||||
.Build();
|
||||
|
||||
var appSettings = new AppSettings();
|
||||
configuration.Bind(appSettings);
|
||||
|
||||
if (String.IsNullOrWhiteSpace(appSettings.HostUrl))
|
||||
{
|
||||
appSettings.HostUrl = "http://0.0.0.0:6500";
|
||||
}
|
||||
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.Enrich.FromLogContext()
|
||||
.Enrich.WithExceptionDetails()
|
||||
.WriteTo.File(appSettings.Logging.File.Path,
|
||||
rollOnFileSizeLimit: true,
|
||||
fileSizeLimitBytes: appSettings.Logging.File.FileSizeLimitBytes,
|
||||
retainedFileCountLimit: appSettings.Logging.File.MaxRollingFiles,
|
||||
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {SourceContext}: {Message:lj}{NewLine}{Exception}")
|
||||
.WriteTo.Console()
|
||||
.MinimumLevel.ControlledBy(LoggingLevelSwitch)
|
||||
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
|
||||
.MinimumLevel.Override("System.Net.Http", LogEventLevel.Warning)
|
||||
.CreateLogger();
|
||||
|
||||
Serilog.Debugging.SelfLog.Enable(msg =>
|
||||
{
|
||||
Debug.Print(msg);
|
||||
Debugger.Break();
|
||||
Console.WriteLine(msg);
|
||||
Debug.WriteLine(msg);
|
||||
});
|
||||
|
||||
return Host.CreateDefaultBuilder(args)
|
||||
.UseWindowsService()
|
||||
.ConfigureServices((_, services) =>
|
||||
{
|
||||
services.AddHostedService<TaskRunner>();
|
||||
services.AddHostedService<UpdateChecker>();
|
||||
})
|
||||
.ConfigureWebHostDefaults(webBuilder =>
|
||||
{
|
||||
webBuilder.UseUrls(appSettings.HostUrl)
|
||||
.UseKestrel()
|
||||
.UseStartup<Startup>();
|
||||
})
|
||||
.UseSerilog();
|
||||
}
|
||||
}
|
||||
appSettings.Port = 6500;
|
||||
}
|
||||
|
||||
builder.WebHost.ConfigureKestrel(options =>
|
||||
{
|
||||
options.ListenAnyIP(appSettings.Port);
|
||||
});
|
||||
|
||||
builder.Host.UseSerilog((_, lc) => lc.Enrich.FromLogContext()
|
||||
.Enrich.WithExceptionDetails()
|
||||
.WriteTo.File(appSettings.Logging.File.Path,
|
||||
rollOnFileSizeLimit: true,
|
||||
fileSizeLimitBytes: appSettings.Logging.File.FileSizeLimitBytes,
|
||||
retainedFileCountLimit: appSettings.Logging.File.MaxRollingFiles,
|
||||
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {SourceContext}: {Message:lj}{NewLine}{Exception}")
|
||||
.WriteTo.Console()
|
||||
.MinimumLevel.ControlledBy(Settings.LoggingLevelSwitch)
|
||||
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
|
||||
.MinimumLevel.Override("System.Net.Http", LogEventLevel.Warning));
|
||||
|
||||
Serilog.Debugging.SelfLog.Enable(msg =>
|
||||
{
|
||||
Debug.Print(msg);
|
||||
Debugger.Break();
|
||||
Console.WriteLine(msg);
|
||||
Debug.WriteLine(msg);
|
||||
});
|
||||
|
||||
Log.Information("Starting RealDebridClient host");
|
||||
|
||||
builder.Services.AddControllers();
|
||||
|
||||
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
|
||||
.AddCookie(options =>
|
||||
{
|
||||
options.SlidingExpiration = true;
|
||||
});
|
||||
|
||||
builder.Services.AddAuthorization();
|
||||
|
||||
builder.Services.AddIdentity<IdentityUser, IdentityRole>(options =>
|
||||
{
|
||||
options.User.RequireUniqueEmail = false;
|
||||
options.Password.RequiredLength = 10;
|
||||
options.Password.RequireUppercase = false;
|
||||
options.Password.RequireLowercase = false;
|
||||
options.Password.RequireNonAlphanumeric = false;
|
||||
options.Password.RequiredUniqueChars = 5;
|
||||
})
|
||||
.AddEntityFrameworkStores<DataContext>()
|
||||
.AddDefaultTokenProviders();
|
||||
|
||||
builder.Services.ConfigureApplicationCookie(options =>
|
||||
{
|
||||
options.Events.OnRedirectToLogin = context =>
|
||||
{
|
||||
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
options.Cookie.Name = "SID";
|
||||
});
|
||||
|
||||
// Configure development cors.
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("Dev",
|
||||
corsBuilder => corsBuilder.AllowAnyMethod()
|
||||
.AllowAnyHeader()
|
||||
.AllowCredentials());
|
||||
});
|
||||
|
||||
// Configure misc services.
|
||||
builder.Services.AddResponseCaching();
|
||||
builder.Services.AddMemoryCache();
|
||||
builder.Services.AddDistributedMemoryCache();
|
||||
builder.Services.AddHttpClient();
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
builder.Services.AddSession();
|
||||
|
||||
builder.Services.AddSpaStaticFiles(spaBuilder =>
|
||||
{
|
||||
spaBuilder.RootPath = "wwwroot";
|
||||
});
|
||||
|
||||
builder.Services.AddSignalR(hubOptions =>
|
||||
{
|
||||
hubOptions.EnableDetailedErrors = true;
|
||||
});
|
||||
|
||||
builder.Host.UseWindowsService();
|
||||
|
||||
RdtClient.Data.DiConfig.Config(builder.Services, appSettings);
|
||||
RdtClient.Service.DiConfig.Config(builder.Services);
|
||||
|
||||
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
|
||||
|
||||
try
|
||||
{
|
||||
// Build the app
|
||||
var app = builder.Build();
|
||||
|
||||
if (builder.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseCors("Dev");
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.ConfigureExceptionHandler();
|
||||
|
||||
app.UseMiddleware<AuthorizeMiddleware>();
|
||||
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
await next.Invoke();
|
||||
|
||||
if (context.Response.StatusCode != 200)
|
||||
{
|
||||
Log.Warning("{StatusCode}: {Value}", context.Response.StatusCode, context.Request.Path.Value);
|
||||
}
|
||||
});
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseAuthentication();
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapHub<RdtHub>("/hub");
|
||||
endpoints.MapControllers();
|
||||
});
|
||||
|
||||
app.MapWhen(x => !x.Request.Path.StartsWithSegments("/api"), routeBuilder =>
|
||||
{
|
||||
routeBuilder.UseSpaStaticFiles();
|
||||
routeBuilder.UseSpa(spa =>
|
||||
{
|
||||
spa.Options.SourcePath = "wwwroot";
|
||||
spa.Options.DefaultPage = "/index.html";
|
||||
});
|
||||
});
|
||||
|
||||
// Run the app
|
||||
app.Run();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Fatal(ex, "Host terminated unexpectedly");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Log.CloseAndFlush();
|
||||
}
|
||||
|
|
@ -5,6 +5,9 @@
|
|||
<OutputType>Exe</OutputType>
|
||||
<UserSecretsId>94c24cba-f03f-4453-a671-3640b517c573</UserSecretsId>
|
||||
<Version>2.0.12</Version>
|
||||
<Nullable>disable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -26,16 +29,16 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="6.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="6.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.3">
|
||||
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="6.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Identity.Core" Version="6.0.3" />
|
||||
<PackageReference Include="Serilog" Version="2.10.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Identity.Core" Version="6.0.4" />
|
||||
<PackageReference Include="Serilog" Version="2.11.0" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="5.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,137 +0,0 @@
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using RdtClient.Data.Data;
|
||||
using RdtClient.Data.Models.Internal;
|
||||
using RdtClient.Service.Middleware;
|
||||
using RdtClient.Service.Services;
|
||||
|
||||
namespace RdtClient.Web
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
private IConfiguration Configuration { get; }
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
var appSettings = new AppSettings();
|
||||
Configuration.Bind(appSettings);
|
||||
|
||||
services.AddSingleton(appSettings);
|
||||
|
||||
var connectionString = $"Data Source={appSettings.Database.Path}";
|
||||
services.AddDbContext<DataContext>(options => options.UseSqlite(connectionString));
|
||||
|
||||
services.AddControllers();
|
||||
|
||||
services.AddSpaStaticFiles(configuration => { configuration.RootPath = "wwwroot"; });
|
||||
|
||||
services.AddSignalR(options =>
|
||||
{
|
||||
options.EnableDetailedErrors = true;
|
||||
});
|
||||
|
||||
services.AddHttpContextAccessor();
|
||||
|
||||
services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("Dev",
|
||||
builder =>
|
||||
{
|
||||
builder.AllowAnyHeader()
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyOrigin();
|
||||
});
|
||||
});
|
||||
|
||||
services.AddHttpClient();
|
||||
|
||||
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
|
||||
.AddCookie(options => { options.SlidingExpiration = true; });
|
||||
|
||||
services.AddAuthorization();
|
||||
|
||||
services.AddIdentity<IdentityUser, IdentityRole>(options =>
|
||||
{
|
||||
options.User.RequireUniqueEmail = false;
|
||||
options.Password.RequiredLength = 10;
|
||||
options.Password.RequireUppercase = false;
|
||||
options.Password.RequireLowercase = false;
|
||||
options.Password.RequireNonAlphanumeric = false;
|
||||
options.Password.RequiredUniqueChars = 5;
|
||||
})
|
||||
.AddEntityFrameworkStores<DataContext>()
|
||||
.AddDefaultTokenProviders();
|
||||
|
||||
services.ConfigureApplicationCookie(options =>
|
||||
{
|
||||
options.Events.OnRedirectToLogin = context =>
|
||||
{
|
||||
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
options.Cookie.Name = "SID";
|
||||
});
|
||||
|
||||
Data.DiConfig.Config(services);
|
||||
Service.DiConfig.Config(services);
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<Startup> logger)
|
||||
{
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseCors("Dev");
|
||||
}
|
||||
|
||||
app.ConfigureExceptionHandler();
|
||||
|
||||
app.UseMiddleware<AuthorizeMiddleware>();
|
||||
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
await next.Invoke();
|
||||
|
||||
if (context.Response.StatusCode != 200)
|
||||
{
|
||||
logger.LogWarning("{StatusCode}: {Value}", context.Response.StatusCode, context.Request.Path.Value);
|
||||
}
|
||||
});
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseAuthentication();
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapHub<RdtHub>("/hub");
|
||||
endpoints.MapControllers();
|
||||
});
|
||||
|
||||
app.MapWhen(x => !x.Request.Path.Value.StartsWith("/api"), builder =>
|
||||
{
|
||||
builder.UseSpaStaticFiles();
|
||||
builder.UseSpa(spa =>
|
||||
{
|
||||
spa.Options.SourcePath = "wwwroot";
|
||||
spa.Options.DefaultPage = "/index.html";
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@
|
|||
"Logging": {
|
||||
"File": {
|
||||
"Path": "C:/Temp/rdtclient/rdtclient.log",
|
||||
"Append": "True",
|
||||
"FileSizeLimitBytes": 5242880,
|
||||
"MaxRollingFiles": 5
|
||||
}
|
||||
|
|
@ -10,6 +9,5 @@
|
|||
"Database": {
|
||||
"Path": "C:/Temp/rdtclient/rdtclient.db"
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"HostUrl": "http://0.0.0.0:6500"
|
||||
"Port": "6500"
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@
|
|||
"Logging": {
|
||||
"File": {
|
||||
"Path": "/data/db/rdtclient.log",
|
||||
"Append": "True",
|
||||
"FileSizeLimitBytes": 5242880,
|
||||
"MaxRollingFiles": 5
|
||||
}
|
||||
|
|
@ -10,6 +9,5 @@
|
|||
"Database": {
|
||||
"Path": "/data/db/rdtclient.db"
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"HostUrl": "http://0.0.0.0:6500"
|
||||
"Port": "6500"
|
||||
}
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/CodeInspection/Browsers/Enable/@EntryValue">False</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=48FF995D_002DAD14_002D4E41_002DBC69_002D7A144C84E3B6/@EntryIndexedValue">ExplicitlyExcluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=92EF8817_002DAD73_002D4301_002D93BD_002D745D7D61DD74_002Fd_003AMigrations/@EntryIndexedValue">ExplicitlyExcluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/GeneratedCode/GeneratedCodeRegions/=_002A_002Eappxmanifest/@EntryIndexedValue">*.appxmanifest</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/GeneratedCode/GeneratedCodeRegions/=_002A_002Ed_002Ets/@EntryIndexedValue">*.d.ts</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/GeneratedCode/GeneratedCodeRegions/=_002A_002Edebug_002Ejs/@EntryIndexedValue">*.debug.js</s:String>
|
||||
|
|
@ -29,6 +27,7 @@
|
|||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeLocalFunctionBody/@EntryIndexedValue">SUGGESTION</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeMethodOrOperatorBody/@EntryIndexedValue">SUGGESTION</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeModifiersOrder/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeNamespaceBody/@EntryIndexedValue">ERROR</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeRedundantParentheses/@EntryIndexedValue">DO_NOT_SHOW</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeThisQualifier/@EntryIndexedValue">ERROR</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeTypeMemberModifiers/@EntryIndexedValue">ERROR</s:String>
|
||||
|
|
@ -178,6 +177,7 @@
|
|||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HCP/@EntryIndexedValue">HCP</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=QA/@EntryIndexedValue">QA</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=QB/@EntryIndexedValue">QB</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=US/@EntryIndexedValue">US</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=XML/@EntryIndexedValue">XML</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Constants/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=LocalConstants/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String>
|
||||
|
|
|
|||
Loading…
Reference in a new issue