Global formatting.
This commit is contained in:
parent
24321a55c9
commit
a9648248f4
91 changed files with 990 additions and 849 deletions
|
|
@ -15,7 +15,7 @@ public class DataContext(DbContextOptions options) : IdentityDbContext(options)
|
||||||
protected override void OnModelCreating(ModelBuilder builder)
|
protected override void OnModelCreating(ModelBuilder builder)
|
||||||
{
|
{
|
||||||
base.OnModelCreating(builder);
|
base.OnModelCreating(builder);
|
||||||
|
|
||||||
var cascadeFKs = builder.Model.GetEntityTypes()
|
var cascadeFKs = builder.Model.GetEntityTypes()
|
||||||
.SelectMany(t => t.GetForeignKeys())
|
.SelectMany(t => t.GetForeignKeys())
|
||||||
.Where(fk => !fk.IsOwnership && fk.DeleteBehavior == DeleteBehavior.Cascade);
|
.Where(fk => !fk.IsOwnership && fk.DeleteBehavior == DeleteBehavior.Cascade);
|
||||||
|
|
@ -25,4 +25,4 @@ public class DataContext(DbContextOptions options) : IdentityDbContext(options)
|
||||||
fk.DeleteBehavior = DeleteBehavior.Restrict;
|
fk.DeleteBehavior = DeleteBehavior.Restrict;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,25 +9,25 @@ public class DownloadData(DataContext dataContext)
|
||||||
public async Task<List<Download>> GetForTorrent(Guid torrentId)
|
public async Task<List<Download>> GetForTorrent(Guid torrentId)
|
||||||
{
|
{
|
||||||
return await dataContext.Downloads
|
return await dataContext.Downloads
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Where(m => m.TorrentId == torrentId)
|
.Where(m => m.TorrentId == torrentId)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Download?> GetById(Guid downloadId)
|
public async Task<Download?> GetById(Guid downloadId)
|
||||||
{
|
{
|
||||||
return await dataContext.Downloads
|
return await dataContext.Downloads
|
||||||
.Include(m => m.Torrent)
|
.Include(m => m.Torrent)
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Download?> Get(Guid torrentId, String path)
|
public async Task<Download?> Get(Guid torrentId, String path)
|
||||||
{
|
{
|
||||||
return await dataContext.Downloads
|
return await dataContext.Downloads
|
||||||
.Include(m => m.Torrent)
|
.Include(m => m.Torrent)
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.FirstOrDefaultAsync(m => m.TorrentId == torrentId && m.Path == path);
|
.FirstOrDefaultAsync(m => m.TorrentId == torrentId && m.Path == path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Download> Add(Guid torrentId, DownloadInfo downloadInfo)
|
public async Task<Download> Add(Guid torrentId, DownloadInfo downloadInfo)
|
||||||
|
|
@ -55,7 +55,7 @@ public class DownloadData(DataContext dataContext)
|
||||||
public async Task UpdateUnrestrictedLink(Guid downloadId, String unrestrictedLink)
|
public async Task UpdateUnrestrictedLink(Guid downloadId, String unrestrictedLink)
|
||||||
{
|
{
|
||||||
var dbDownload = await dataContext.Downloads
|
var dbDownload = await dataContext.Downloads
|
||||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||||
|
|
||||||
if (dbDownload == null)
|
if (dbDownload == null)
|
||||||
{
|
{
|
||||||
|
|
@ -72,7 +72,7 @@ public class DownloadData(DataContext dataContext)
|
||||||
public async Task UpdateFileName(Guid downloadId, String fileName)
|
public async Task UpdateFileName(Guid downloadId, String fileName)
|
||||||
{
|
{
|
||||||
var dbDownload = await dataContext.Downloads
|
var dbDownload = await dataContext.Downloads
|
||||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||||
|
|
||||||
if (dbDownload == null)
|
if (dbDownload == null)
|
||||||
{
|
{
|
||||||
|
|
@ -89,7 +89,7 @@ public class DownloadData(DataContext dataContext)
|
||||||
public async Task UpdateDownloadStarted(Guid downloadId, DateTimeOffset? dateTime)
|
public async Task UpdateDownloadStarted(Guid downloadId, DateTimeOffset? dateTime)
|
||||||
{
|
{
|
||||||
var dbDownload = await dataContext.Downloads
|
var dbDownload = await dataContext.Downloads
|
||||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||||
|
|
||||||
if (dbDownload == null)
|
if (dbDownload == null)
|
||||||
{
|
{
|
||||||
|
|
@ -106,13 +106,13 @@ public class DownloadData(DataContext dataContext)
|
||||||
public async Task UpdateDownloadFinished(Guid downloadId, DateTimeOffset? dateTime)
|
public async Task UpdateDownloadFinished(Guid downloadId, DateTimeOffset? dateTime)
|
||||||
{
|
{
|
||||||
var dbDownload = await dataContext.Downloads
|
var dbDownload = await dataContext.Downloads
|
||||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||||
|
|
||||||
if (dbDownload == null)
|
if (dbDownload == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dbDownload.DownloadFinished = dateTime;
|
dbDownload.DownloadFinished = dateTime;
|
||||||
|
|
||||||
await dataContext.SaveChangesAsync();
|
await dataContext.SaveChangesAsync();
|
||||||
|
|
@ -123,7 +123,7 @@ public class DownloadData(DataContext dataContext)
|
||||||
public async Task UpdateUnpackingQueued(Guid downloadId, DateTimeOffset? dateTime)
|
public async Task UpdateUnpackingQueued(Guid downloadId, DateTimeOffset? dateTime)
|
||||||
{
|
{
|
||||||
var dbDownload = await dataContext.Downloads
|
var dbDownload = await dataContext.Downloads
|
||||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||||
|
|
||||||
if (dbDownload == null)
|
if (dbDownload == null)
|
||||||
{
|
{
|
||||||
|
|
@ -140,7 +140,7 @@ public class DownloadData(DataContext dataContext)
|
||||||
public async Task UpdateUnpackingStarted(Guid downloadId, DateTimeOffset? dateTime)
|
public async Task UpdateUnpackingStarted(Guid downloadId, DateTimeOffset? dateTime)
|
||||||
{
|
{
|
||||||
var dbDownload = await dataContext.Downloads
|
var dbDownload = await dataContext.Downloads
|
||||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||||
|
|
||||||
if (dbDownload == null)
|
if (dbDownload == null)
|
||||||
{
|
{
|
||||||
|
|
@ -157,7 +157,7 @@ public class DownloadData(DataContext dataContext)
|
||||||
public async Task UpdateUnpackingFinished(Guid downloadId, DateTimeOffset? dateTime)
|
public async Task UpdateUnpackingFinished(Guid downloadId, DateTimeOffset? dateTime)
|
||||||
{
|
{
|
||||||
var dbDownload = await dataContext.Downloads
|
var dbDownload = await dataContext.Downloads
|
||||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||||
|
|
||||||
if (dbDownload == null)
|
if (dbDownload == null)
|
||||||
{
|
{
|
||||||
|
|
@ -170,11 +170,11 @@ public class DownloadData(DataContext dataContext)
|
||||||
|
|
||||||
await TorrentData.VoidCache();
|
await TorrentData.VoidCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateCompleted(Guid downloadId, DateTimeOffset? dateTime)
|
public async Task UpdateCompleted(Guid downloadId, DateTimeOffset? dateTime)
|
||||||
{
|
{
|
||||||
var dbDownload = await dataContext.Downloads
|
var dbDownload = await dataContext.Downloads
|
||||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||||
|
|
||||||
if (dbDownload == null)
|
if (dbDownload == null)
|
||||||
{
|
{
|
||||||
|
|
@ -191,7 +191,7 @@ public class DownloadData(DataContext dataContext)
|
||||||
public async Task UpdateError(Guid downloadId, String? error)
|
public async Task UpdateError(Guid downloadId, String? error)
|
||||||
{
|
{
|
||||||
var dbDownload = await dataContext.Downloads
|
var dbDownload = await dataContext.Downloads
|
||||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||||
|
|
||||||
if (dbDownload == null)
|
if (dbDownload == null)
|
||||||
{
|
{
|
||||||
|
|
@ -204,11 +204,11 @@ public class DownloadData(DataContext dataContext)
|
||||||
|
|
||||||
await TorrentData.VoidCache();
|
await TorrentData.VoidCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateRetryCount(Guid downloadId, Int32 retryCount)
|
public async Task UpdateRetryCount(Guid downloadId, Int32 retryCount)
|
||||||
{
|
{
|
||||||
var dbDownload = await dataContext.Downloads
|
var dbDownload = await dataContext.Downloads
|
||||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||||
|
|
||||||
if (dbDownload == null)
|
if (dbDownload == null)
|
||||||
{
|
{
|
||||||
|
|
@ -225,7 +225,7 @@ public class DownloadData(DataContext dataContext)
|
||||||
public async Task UpdateRemoteId(Guid downloadId, String remoteId)
|
public async Task UpdateRemoteId(Guid downloadId, String remoteId)
|
||||||
{
|
{
|
||||||
var dbDownload = await dataContext.Downloads
|
var dbDownload = await dataContext.Downloads
|
||||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
.FirstOrDefaultAsync(m => m.DownloadId == downloadId);
|
||||||
|
|
||||||
if (dbDownload == null)
|
if (dbDownload == null)
|
||||||
{
|
{
|
||||||
|
|
@ -240,8 +240,8 @@ public class DownloadData(DataContext dataContext)
|
||||||
public async Task DeleteForTorrent(Guid torrentId)
|
public async Task DeleteForTorrent(Guid torrentId)
|
||||||
{
|
{
|
||||||
var downloads = await dataContext.Downloads
|
var downloads = await dataContext.Downloads
|
||||||
.Where(m => m.TorrentId == torrentId)
|
.Where(m => m.TorrentId == torrentId)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
dataContext.Downloads.RemoveRange(downloads);
|
dataContext.Downloads.RemoveRange(downloads);
|
||||||
|
|
||||||
|
|
@ -253,7 +253,7 @@ public class DownloadData(DataContext dataContext)
|
||||||
public async Task Reset(Guid downloadId)
|
public async Task Reset(Guid downloadId)
|
||||||
{
|
{
|
||||||
var dbDownload = await dataContext.Downloads
|
var dbDownload = await dataContext.Downloads
|
||||||
.FirstOrDefaultAsync(m => m.DownloadId == downloadId)
|
.FirstOrDefaultAsync(m => m.DownloadId == downloadId)
|
||||||
?? throw new($"Cannot find download with ID {downloadId}");
|
?? throw new($"Cannot find download with ID {downloadId}");
|
||||||
|
|
||||||
dbDownload.RetryCount = 0;
|
dbDownload.RetryCount = 0;
|
||||||
|
|
@ -272,4 +272,4 @@ public class DownloadData(DataContext dataContext)
|
||||||
|
|
||||||
await TorrentData.VoidCache();
|
await TorrentData.VoidCache();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,11 +67,14 @@ public class SettingData(DataContext dataContext, ILogger<SettingData> logger)
|
||||||
{
|
{
|
||||||
var dbSettings = await dataContext.Settings.AsNoTracking().ToListAsync();
|
var dbSettings = await dataContext.Settings.AsNoTracking().ToListAsync();
|
||||||
|
|
||||||
var expectedSettings = GetSettings(Get, null).Where(m => m.Type != "Object").Select(m => new Setting
|
var expectedSettings = GetSettings(Get, null)
|
||||||
{
|
.Where(m => m.Type != "Object")
|
||||||
SettingId = m.Key,
|
.Select(m => new Setting
|
||||||
Value = m.Value?.ToString()
|
{
|
||||||
}).ToList();
|
SettingId = m.Key,
|
||||||
|
Value = m.Value?.ToString()
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
var newSettings = expectedSettings.Where(m => dbSettings.All(p => p.SettingId != m.SettingId)).ToList();
|
var newSettings = expectedSettings.Where(m => dbSettings.All(p => p.SettingId != m.SettingId)).ToList();
|
||||||
|
|
||||||
|
|
@ -215,4 +218,4 @@ public class SettingData(DataContext dataContext, ILogger<SettingData> logger)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,9 @@ public class TorrentData(DataContext dataContext) : ITorrentData
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_torrentCache ??= await dataContext.Torrents
|
_torrentCache ??= await dataContext.Torrents
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Include(m => m.Downloads)
|
.Include(m => m.Downloads)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
return [.. _torrentCache.OrderBy(m => m.Priority ?? 9999).ThenBy(m => m.Added)];
|
return [.. _torrentCache.OrderBy(m => m.Priority ?? 9999).ThenBy(m => m.Added)];
|
||||||
}
|
}
|
||||||
|
|
@ -32,9 +32,9 @@ public class TorrentData(DataContext dataContext) : ITorrentData
|
||||||
public async Task<Torrent?> GetById(Guid torrentId)
|
public async Task<Torrent?> GetById(Guid torrentId)
|
||||||
{
|
{
|
||||||
var dbTorrent = await dataContext.Torrents
|
var dbTorrent = await dataContext.Torrents
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Include(m => m.Downloads)
|
.Include(m => m.Downloads)
|
||||||
.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
|
.FirstOrDefaultAsync(m => m.TorrentId == torrentId);
|
||||||
|
|
||||||
if (dbTorrent == null)
|
if (dbTorrent == null)
|
||||||
{
|
{
|
||||||
|
|
@ -54,9 +54,9 @@ public class TorrentData(DataContext dataContext) : ITorrentData
|
||||||
hash = hash.ToLower();
|
hash = hash.ToLower();
|
||||||
|
|
||||||
var dbTorrent = await dataContext.Torrents
|
var dbTorrent = await dataContext.Torrents
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Include(m => m.Downloads)
|
.Include(m => m.Downloads)
|
||||||
.FirstOrDefaultAsync(m => m.Hash == hash);
|
.FirstOrDefaultAsync(m => m.Hash == hash);
|
||||||
|
|
||||||
if (dbTorrent == null)
|
if (dbTorrent == null)
|
||||||
{
|
{
|
||||||
|
|
@ -135,7 +135,7 @@ public class TorrentData(DataContext dataContext) : ITorrentData
|
||||||
dbTorrent.RdSpeed = torrent.RdSpeed;
|
dbTorrent.RdSpeed = torrent.RdSpeed;
|
||||||
dbTorrent.RdSeeders = torrent.RdSeeders;
|
dbTorrent.RdSeeders = torrent.RdSeeders;
|
||||||
dbTorrent.RdFiles = torrent.RdFiles;
|
dbTorrent.RdFiles = torrent.RdFiles;
|
||||||
|
|
||||||
await dataContext.SaveChangesAsync();
|
await dataContext.SaveChangesAsync();
|
||||||
|
|
||||||
await VoidCache();
|
await VoidCache();
|
||||||
|
|
@ -149,7 +149,7 @@ public class TorrentData(DataContext dataContext) : ITorrentData
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dbTorrent.RdId = rdId;
|
dbTorrent.RdId = rdId;
|
||||||
|
|
||||||
await dataContext.SaveChangesAsync();
|
await dataContext.SaveChangesAsync();
|
||||||
|
|
@ -327,4 +327,4 @@ public class TorrentData(DataContext dataContext) : ITorrentData
|
||||||
TorrentCacheLock.Release();
|
TorrentCacheLock.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,4 +9,4 @@ public class UserData(DataContext dataContext)
|
||||||
{
|
{
|
||||||
return await dataContext.Users.OrderBy(m => m.Id).FirstOrDefaultAsync();
|
return await dataContext.Users.OrderBy(m => m.Id).FirstOrDefaultAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,4 +22,4 @@ public static class DiConfig
|
||||||
services.AddScoped<ITorrentData, TorrentData>();
|
services.AddScoped<ITorrentData, TorrentData>();
|
||||||
services.AddScoped<UserData>();
|
services.AddScoped<UserData>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,4 +9,4 @@ public enum AuthenticationType
|
||||||
|
|
||||||
[Description("No Authentication")]
|
[Description("No Authentication")]
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,5 +14,5 @@ public enum DownloadClient
|
||||||
Symlink,
|
Symlink,
|
||||||
|
|
||||||
[Description("Synology DownloadStation")]
|
[Description("Synology DownloadStation")]
|
||||||
DownloadStation,
|
DownloadStation
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,4 +21,4 @@ public enum DownloadClientLogLevel
|
||||||
|
|
||||||
[Description("None")]
|
[Description("None")]
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,4 +18,4 @@ public enum LogLevel
|
||||||
|
|
||||||
[Description("Error")]
|
[Description("Error")]
|
||||||
Error
|
Error
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,4 +18,4 @@ public enum Provider
|
||||||
|
|
||||||
[Description("DebridLink")]
|
[Description("DebridLink")]
|
||||||
DebridLink
|
DebridLink
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,4 +12,4 @@ public enum TorrentDownloadAction
|
||||||
|
|
||||||
[Description("Manually Select Files")]
|
[Description("Manually Select Files")]
|
||||||
DownloadManual = 2
|
DownloadManual = 2
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ public enum TorrentFinishedAction
|
||||||
|
|
||||||
[Description("Remove Torrent From Provider")]
|
[Description("Remove Torrent From Provider")]
|
||||||
RemoveRealDebrid = 2,
|
RemoveRealDebrid = 2,
|
||||||
|
|
||||||
[Description("Remove Torrent From Client")]
|
[Description("Remove Torrent From Client")]
|
||||||
RemoveClient = 3
|
RemoveClient = 3
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,5 +8,5 @@ public enum TorrentHostDownloadAction
|
||||||
DownloadAll = 0,
|
DownloadAll = 0,
|
||||||
|
|
||||||
[Description("Don't download any files to host")]
|
[Description("Don't download any files to host")]
|
||||||
DownloadNone = 1,
|
DownloadNone = 1
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,4 +11,4 @@ public enum TorrentStatus
|
||||||
Uploading = 5,
|
Uploading = 5,
|
||||||
|
|
||||||
Error = 99
|
Error = 99
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,17 +44,19 @@ public class Download
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used to create <see cref="Download"/>s
|
/// Used to create <see cref="Download" />s
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DownloadInfo
|
public class DownloadInfo
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The name of the file. Should not include directory.
|
/// The name of the file. Should not include directory.
|
||||||
/// If the filename is not known, set tn null and `GetFileName` will be called with the unrestricted link.
|
/// If the filename is not known, set tn null and `GetFileName` will be called with the unrestricted link.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public required String? FileName;
|
public required String? FileName;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The restricted link to download this download. If the debrid serice in question does not have restricted links, use either a fake or the unrestricted link
|
/// The restricted link to download this download. If the debrid serice in question does not have restricted links, use
|
||||||
|
/// either a fake or the unrestricted link
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public required String RestrictedLink;
|
public required String RestrictedLink;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,4 +8,4 @@ public class Setting
|
||||||
public String SettingId { get; set; } = null!;
|
public String SettingId { get; set; } = null!;
|
||||||
|
|
||||||
public String? Value { get; set; }
|
public String? Value { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,10 @@ public class Torrent
|
||||||
public String Hash { get; set; } = null!;
|
public String Hash { get; set; } = null!;
|
||||||
|
|
||||||
public String? Category { get; set; }
|
public String? Category { get; set; }
|
||||||
|
|
||||||
public TorrentDownloadAction DownloadAction { get; set; }
|
public TorrentDownloadAction DownloadAction { get; set; }
|
||||||
public TorrentFinishedAction FinishedAction { get; set; }
|
public TorrentFinishedAction FinishedAction { get; set; }
|
||||||
public Int32 FinishedActionDelay { get; set; }
|
public Int32 FinishedActionDelay { get; set; }
|
||||||
public TorrentHostDownloadAction HostDownloadAction { get; set; }
|
public TorrentHostDownloadAction HostDownloadAction { get; set; }
|
||||||
public Int32 DownloadMinSize { get; set; }
|
public Int32 DownloadMinSize { get; set; }
|
||||||
public String? IncludeRegex { get; set; }
|
public String? IncludeRegex { get; set; }
|
||||||
|
|
@ -94,4 +94,4 @@ public class Torrent
|
||||||
return DownloadManualFiles.Split(",");
|
return DownloadManualFiles.Split(",");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ public class AppSettings
|
||||||
{
|
{
|
||||||
public AppSettingsLogging? Logging { get; set; }
|
public AppSettingsLogging? Logging { get; set; }
|
||||||
public AppSettingsDatabase? Database { get; set; }
|
public AppSettingsDatabase? Database { get; set; }
|
||||||
|
|
||||||
public Int32 Port { get; set; }
|
public Int32 Port { get; set; }
|
||||||
public String? BasePath { get; set; }
|
public String? BasePath { get; set; }
|
||||||
}
|
}
|
||||||
|
|
@ -13,7 +13,7 @@ public class AppSettingsLogging
|
||||||
{
|
{
|
||||||
public AppSettingsLoggingFile? File { get; set; }
|
public AppSettingsLoggingFile? File { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AppSettingsLoggingFile
|
public class AppSettingsLoggingFile
|
||||||
{
|
{
|
||||||
public String? Path { get; set; }
|
public String? Path { get; set; }
|
||||||
|
|
@ -24,4 +24,4 @@ public class AppSettingsLoggingFile
|
||||||
public class AppSettingsDatabase
|
public class AppSettingsDatabase
|
||||||
{
|
{
|
||||||
public String? Path { get; set; }
|
public String? Path { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
using RdtClient.Data.Enums;
|
using System.ComponentModel;
|
||||||
using System.ComponentModel;
|
using RdtClient.Data.Enums;
|
||||||
|
|
||||||
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global
|
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global
|
||||||
|
|
||||||
|
|
@ -155,6 +155,7 @@ http://127.0.0.1:6800/jsonrpc.")]
|
||||||
[DisplayName("Synology DownloadStation Username")]
|
[DisplayName("Synology DownloadStation Username")]
|
||||||
[Description("The username to use when connecting to the Synology DownloadStation.")]
|
[Description("The username to use when connecting to the Synology DownloadStation.")]
|
||||||
public String? DownloadStationUsername { get; set; } = null;
|
public String? DownloadStationUsername { get; set; } = null;
|
||||||
|
|
||||||
[DisplayName("Synology DownloadStation Password")]
|
[DisplayName("Synology DownloadStation Password")]
|
||||||
[Description("The password to use when connecting to the Synology DownloadStation.")]
|
[Description("The password to use when connecting to the Synology DownloadStation.")]
|
||||||
public String? DownloadStationPassword { get; set; } = null;
|
public String? DownloadStationPassword { get; set; } = null;
|
||||||
|
|
@ -202,7 +203,7 @@ or
|
||||||
public String ApiKey { get; set; } = "";
|
public String ApiKey { get; set; } = "";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// API hostname to use <b>for Real Debrid only</b>
|
/// API hostname to use <b>for Real Debrid only</b>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DisplayName("API Hostname (RD only)")]
|
[DisplayName("API Hostname (RD only)")]
|
||||||
[Description("Use this instead of the normal hostname for Real Debrid API requests. Only used by Real Debrid. Leave blank to use default.")]
|
[Description("Use this instead of the normal hostname for Real Debrid API requests. Only used by Real Debrid. Leave blank to use default.")]
|
||||||
|
|
@ -281,7 +282,7 @@ public class DbSettingsDefaultsWithCategory : DbSettingsDefaults
|
||||||
[DisplayName("Post Download Action")]
|
[DisplayName("Post Download Action")]
|
||||||
[Description("When all files are downloaded from the provider to the host, perform this action. Does not apply when using the symlink downloader.")]
|
[Description("When all files are downloaded from the provider to the host, perform this action. Does not apply when using the symlink downloader.")]
|
||||||
public TorrentFinishedAction FinishedAction { get; set; } = TorrentFinishedAction.RemoveAllTorrents;
|
public TorrentFinishedAction FinishedAction { get; set; } = TorrentFinishedAction.RemoveAllTorrents;
|
||||||
|
|
||||||
[DisplayName("Finished Action Delay")]
|
[DisplayName("Finished Action Delay")]
|
||||||
[Description("When all files are downloaded from the provider to the host, wait this many minutes before performing the action above.")]
|
[Description("When all files are downloaded from the provider to the host, wait this many minutes before performing the action above.")]
|
||||||
public Int32 FinishedActionDelay { get; set; } = 0;
|
public Int32 FinishedActionDelay { get; set; } = 0;
|
||||||
|
|
@ -324,4 +325,4 @@ public class DbSettingsDefaults
|
||||||
[DisplayName("Priority")]
|
[DisplayName("Priority")]
|
||||||
[Description("Set the priority of a torrent, 1 = highest, 0 = disabled.")]
|
[Description("Set the priority of a torrent, 1 = highest, 0 = disabled.")]
|
||||||
public Int32 Priority { get; set; } = 0;
|
public Int32 Priority { get; set; } = 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,4 +9,4 @@ public class Profile
|
||||||
public String? LatestVersion { get; set; }
|
public String? LatestVersion { get; set; }
|
||||||
public Boolean? IsInsecure { get; set; }
|
public Boolean? IsInsecure { get; set; }
|
||||||
public Boolean? DisableUpdateNotification { get; set; }
|
public Boolean? DisableUpdateNotification { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,4 +8,4 @@ public class SettingProperty
|
||||||
public String? Description { get; set; }
|
public String? Description { get; set; }
|
||||||
public String Type { get; set; } = default!;
|
public String Type { get; set; } = default!;
|
||||||
public Dictionary<Int32, String>? EnumValues { get; set; }
|
public Dictionary<Int32, String>? EnumValues { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,4 +21,4 @@ public class AppBuildInfo
|
||||||
|
|
||||||
[JsonPropertyName("zlib")]
|
[JsonPropertyName("zlib")]
|
||||||
public String? Zlib { get; set; }
|
public String? Zlib { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -427,4 +427,4 @@ public class AppPreferences
|
||||||
|
|
||||||
public class ScanDirs
|
public class ScanDirs
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -99,4 +99,4 @@ public class SyncMetaDataServerState
|
||||||
|
|
||||||
[JsonPropertyName("write_cache_overload")]
|
[JsonPropertyName("write_cache_overload")]
|
||||||
public String? WriteCacheOverload { get; set; }
|
public String? WriteCacheOverload { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,4 +9,4 @@ public class TorrentCategory
|
||||||
|
|
||||||
[JsonPropertyName("savePath")]
|
[JsonPropertyName("savePath")]
|
||||||
public String? SavePath { get; set; }
|
public String? SavePath { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,4 @@ public class TorrentFileItem
|
||||||
{
|
{
|
||||||
[JsonPropertyName("name")]
|
[JsonPropertyName("name")]
|
||||||
public String? Name { get; set; }
|
public String? Name { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ public class TorrentInfo
|
||||||
|
|
||||||
[JsonPropertyName("completion_on")]
|
[JsonPropertyName("completion_on")]
|
||||||
public Int64? CompletionOn { get; set; }
|
public Int64? CompletionOn { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("content_path")]
|
[JsonPropertyName("content_path")]
|
||||||
public String? ContentPath { get; set; }
|
public String? ContentPath { get; set; }
|
||||||
|
|
||||||
|
|
@ -135,4 +135,4 @@ public class TorrentInfo
|
||||||
|
|
||||||
[JsonPropertyName("upspeed")]
|
[JsonPropertyName("upspeed")]
|
||||||
public Int64? Upspeed { get; set; }
|
public Int64? Upspeed { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -102,4 +102,4 @@ public class TorrentProperties
|
||||||
|
|
||||||
[JsonPropertyName("up_speed_avg")]
|
[JsonPropertyName("up_speed_avg")]
|
||||||
public Int64? UpSpeedAvg { get; set; }
|
public Int64? UpSpeedAvg { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,4 +27,4 @@ public class TransferInfo
|
||||||
|
|
||||||
[JsonPropertyName("up_rate_limit")]
|
[JsonPropertyName("up_rate_limit")]
|
||||||
public Int64 UpRateLimit { get; set; }
|
public Int64 UpRateLimit { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,4 +5,4 @@ public class TorrentClientAvailableFile
|
||||||
public String Filename { get; set; } = default!;
|
public String Filename { get; set; } = default!;
|
||||||
|
|
||||||
public Int64 Filesize { get; set; }
|
public Int64 Filesize { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,4 +7,4 @@ public class TorrentClientFile
|
||||||
public Int64 Bytes { get; set; }
|
public Int64 Bytes { get; set; }
|
||||||
public Boolean Selected { get; set; }
|
public Boolean Selected { get; set; }
|
||||||
public String? DownloadLink { get; set; }
|
public String? DownloadLink { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,4 +20,4 @@ public class TorrentClientTorrent
|
||||||
public DateTimeOffset? Ended { get; set; }
|
public DateTimeOffset? Ended { get; set; }
|
||||||
public Int64? Speed { get; set; }
|
public Int64? Speed { get; set; }
|
||||||
public Int64? Seeders { get; set; }
|
public Int64? Seeders { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,4 +4,4 @@ public class TorrentClientUser
|
||||||
{
|
{
|
||||||
public String? Username { get; set; }
|
public String? Username { get; set; }
|
||||||
public DateTimeOffset? Expiration { get; set; }
|
public DateTimeOffset? Expiration { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
14
server/RdtClient.Service.Test/GlobalSuppressions.cs
Normal file
14
server/RdtClient.Service.Test/GlobalSuppressions.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
[assembly:
|
||||||
|
SuppressMessage("Usage",
|
||||||
|
"xUnit1045:Avoid using TheoryData type arguments that might not be serializable",
|
||||||
|
Justification = "It is serializable.",
|
||||||
|
Scope = "NamespaceAndDescendants",
|
||||||
|
Target = "N:RdtClient.Service.Test")]
|
||||||
|
[assembly:
|
||||||
|
SuppressMessage("Performance",
|
||||||
|
"SYSLIB1045:Convert to 'GeneratedRegexAttribute'.",
|
||||||
|
Justification = "We don't care for unit tests.",
|
||||||
|
Scope = "NamespaceAndDescendants",
|
||||||
|
Target = "N:RdtClient.Service.Test")]
|
||||||
|
|
@ -17,7 +17,7 @@ public class DownloadableFileFilterTest
|
||||||
{
|
{
|
||||||
RdId = "1"
|
RdId = "1"
|
||||||
};
|
};
|
||||||
|
|
||||||
var fileFilter = new DownloadableFileFilter(mocks.LoggerMock.Object);
|
var fileFilter = new DownloadableFileFilter(mocks.LoggerMock.Object);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -28,10 +28,11 @@ public class DownloadableFileFilterTest
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
||||||
// downloadMinSize is in MB, fileSize is in B
|
// downloadMinSize is in MB, fileSize is in B
|
||||||
[InlineData(100, 20 * 1024 * 1024)]
|
[InlineData(100, 20 * 1024 * 1024)]
|
||||||
[InlineData(2, 2 * 1024 * 1024)]
|
[InlineData(2, 2 * 1024 * 1024)]
|
||||||
[InlineData(2, 2 * (1000 * 1000 + 1))] // mostly to show we use 1024 not 1000 for conversion
|
[InlineData(2, 2 * ((1000 * 1000) + 1))] // mostly to show we use 1024 not 1000 for conversion
|
||||||
public void IsDownloadable_WhenDownloadMinSizeSpecified_AndDownloadBelowSize_ReturnsFalse(Int32 downloadMinSize, Int64 fileSize)
|
public void IsDownloadable_WhenDownloadMinSizeSpecified_AndDownloadBelowSize_ReturnsFalse(Int32 downloadMinSize, Int64 fileSize)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
@ -51,10 +52,10 @@ public class DownloadableFileFilterTest
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(result);
|
Assert.False(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(100, 110 * 1024 * 1024)]
|
[InlineData(100, 110 * 1024 * 1024)]
|
||||||
[InlineData(2, 2 * 1024 * 1024 + 1)]
|
[InlineData(2, (2 * 1024 * 1024) + 1)]
|
||||||
public void IsDownloadable_WhenDownloadMinSizeSpecified_AndDownloadAboveSize_ReturnsTrue(Int32 downloadMinSize, Int64 fileSize)
|
public void IsDownloadable_WhenDownloadMinSizeSpecified_AndDownloadAboveSize_ReturnsTrue(Int32 downloadMinSize, Int64 fileSize)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
|
@ -74,7 +75,7 @@ public class DownloadableFileFilterTest
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(result);
|
Assert.True(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("file", "no-match")]
|
[InlineData("file", "no-match")]
|
||||||
[InlineData("file", "even/in/a/subdirectory.txt")]
|
[InlineData("file", "even/in/a/subdirectory.txt")]
|
||||||
|
|
@ -124,7 +125,7 @@ public class DownloadableFileFilterTest
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(result);
|
Assert.True(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("file", "no-match")]
|
[InlineData("file", "no-match")]
|
||||||
[InlineData("file", "even/in/a/subdirectory.txt")]
|
[InlineData("file", "even/in/a/subdirectory.txt")]
|
||||||
|
|
@ -202,23 +203,22 @@ public class DownloadableFileFilterTest
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(10, "file", 10 * 1024 * 1024 + 1, "no-match.txt")]
|
[InlineData(10, "file", (10 * 1024 * 1024) + 1, "no-match.txt")]
|
||||||
public void IsDownloadable_WhenBothDownloadMinSizeAndIncludeRegexSpecified_AndDownloadAboveSizeAndDoesNotMatchRegex_ReturnsFalse(
|
public void IsDownloadable_WhenBothDownloadMinSizeAndIncludeRegexSpecified_AndDownloadAboveSizeAndDoesNotMatchRegex_ReturnsFalse(Int32 minSize,
|
||||||
Int32 minSize,
|
|
||||||
String includeRegex,
|
String includeRegex,
|
||||||
Int64 fileSize,
|
Int64 fileSize,
|
||||||
String filePath)
|
String filePath)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var mocks = new Mocks();
|
var mocks = new Mocks();
|
||||||
|
|
||||||
var torrent = new Torrent
|
var torrent = new Torrent
|
||||||
{
|
{
|
||||||
RdId = "1",
|
RdId = "1",
|
||||||
IncludeRegex = includeRegex,
|
IncludeRegex = includeRegex,
|
||||||
DownloadMinSize = minSize
|
DownloadMinSize = minSize
|
||||||
};
|
};
|
||||||
|
|
||||||
var fileFilter = new DownloadableFileFilter(mocks.LoggerMock.Object);
|
var fileFilter = new DownloadableFileFilter(mocks.LoggerMock.Object);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -227,25 +227,24 @@ public class DownloadableFileFilterTest
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(result);
|
Assert.False(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(10, "file", 10 * 1024 * 1024 - 1, "file.txt")]
|
[InlineData(10, "file", (10 * 1024 * 1024) - 1, "file.txt")]
|
||||||
public void IsDownloadable_WhenBothDownloadMinSizeAndIncludeRegexSpecified_AndDownloadBelowSizeAndMatchesRegex_ReturnsFalse(
|
public void IsDownloadable_WhenBothDownloadMinSizeAndIncludeRegexSpecified_AndDownloadBelowSizeAndMatchesRegex_ReturnsFalse(Int32 minSize,
|
||||||
Int32 minSize,
|
|
||||||
String includeRegex,
|
String includeRegex,
|
||||||
Int64 fileSize,
|
Int64 fileSize,
|
||||||
String filePath)
|
String filePath)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var mocks = new Mocks();
|
var mocks = new Mocks();
|
||||||
|
|
||||||
var torrent = new Torrent
|
var torrent = new Torrent
|
||||||
{
|
{
|
||||||
RdId = "1",
|
RdId = "1",
|
||||||
IncludeRegex = includeRegex,
|
IncludeRegex = includeRegex,
|
||||||
DownloadMinSize = minSize
|
DownloadMinSize = minSize
|
||||||
};
|
};
|
||||||
|
|
||||||
var fileFilter = new DownloadableFileFilter(mocks.LoggerMock.Object);
|
var fileFilter = new DownloadableFileFilter(mocks.LoggerMock.Object);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -254,8 +253,9 @@ public class DownloadableFileFilterTest
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(result);
|
Assert.False(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Mocks
|
private class Mocks
|
||||||
{
|
{
|
||||||
public readonly Mock<ILogger<DownloadableFileFilter>> LoggerMock = new();
|
public readonly Mock<ILogger<DownloadableFileFilter>> LoggerMock = new();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ using Synology.Api.Client.Apis.DownloadStation.Task.Models;
|
||||||
|
|
||||||
namespace RdtClient.Service.Test.Services.Downloaders;
|
namespace RdtClient.Service.Test.Services.Downloaders;
|
||||||
|
|
||||||
class Mocks
|
internal class Mocks
|
||||||
{
|
{
|
||||||
public readonly String Gid;
|
public readonly String Gid;
|
||||||
public readonly Mock<ISynologyClient> SynologyClientMock = new();
|
public readonly Mock<ISynologyClient> SynologyClientMock = new();
|
||||||
|
|
@ -22,7 +22,7 @@ class Mocks
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FakeDelayProvider : IDelayProvider
|
internal class FakeDelayProvider : IDelayProvider
|
||||||
{
|
{
|
||||||
public Task Delay(Int32 milliseconds)
|
public Task Delay(Int32 milliseconds)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,18 @@
|
||||||
|
using System.Web;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using MonoTorrent.BEncoding;
|
||||||
using Moq;
|
using Moq;
|
||||||
using RdtClient.Service.Services;
|
using RdtClient.Service.Services;
|
||||||
using MonoTorrent.BEncoding;
|
|
||||||
|
|
||||||
namespace RdtClient.Service.Test.Services;
|
namespace RdtClient.Service.Test.Services;
|
||||||
|
|
||||||
public class EnricherTest : IDisposable
|
public class EnricherTest : IDisposable
|
||||||
{
|
{
|
||||||
private readonly MockRepository _mockRepository;
|
private const String TestMagnetLink =
|
||||||
|
"magnet:?xt=urn:btih:0123456789abcdef0123456789abcdef01234567&dn=TestFile&tr=http%3A%2F%2Ftracker1.com%2Fannounce&tr=http%3A%2F%2Ftracker2.com%2Fannounce";
|
||||||
|
|
||||||
private readonly Mock<ILogger<Enricher>> _loggerMock;
|
private readonly Mock<ILogger<Enricher>> _loggerMock;
|
||||||
|
private readonly MockRepository _mockRepository;
|
||||||
private readonly Mock<ITrackerListGrabber> _trackerListGrabberMock;
|
private readonly Mock<ITrackerListGrabber> _trackerListGrabberMock;
|
||||||
|
|
||||||
public EnricherTest()
|
public EnricherTest()
|
||||||
|
|
@ -23,9 +27,6 @@ public class EnricherTest : IDisposable
|
||||||
_mockRepository.VerifyAll();
|
_mockRepository.VerifyAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
private const String TestMagnetLink =
|
|
||||||
"magnet:?xt=urn:btih:0123456789abcdef0123456789abcdef01234567&dn=TestFile&tr=http%3A%2F%2Ftracker1.com%2Fannounce&tr=http%3A%2F%2Ftracker2.com%2Fannounce";
|
|
||||||
|
|
||||||
// Helper methods for creating BEncodedDictionary objects for torrents
|
// Helper methods for creating BEncodedDictionary objects for torrents
|
||||||
private static BEncodedDictionary CreateStandardTorrentDict(String announceUrl, List<String>? announceListTier = null)
|
private static BEncodedDictionary CreateStandardTorrentDict(String announceUrl, List<String>? announceListTier = null)
|
||||||
{
|
{
|
||||||
|
|
@ -216,7 +217,7 @@ public class EnricherTest : IDisposable
|
||||||
var result = await enricher.EnrichMagnetLink(magnetLink);
|
var result = await enricher.EnrichMagnetLink(magnetLink);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var queryParams = System.Web.HttpUtility.ParseQueryString(new Uri(result).Query);
|
var queryParams = HttpUtility.ParseQueryString(new Uri(result).Query);
|
||||||
Assert.Equal("urn:btih:HASH", queryParams["xt"]);
|
Assert.Equal("urn:btih:HASH", queryParams["xt"]);
|
||||||
Assert.Equal("MyFile", queryParams["dn"]);
|
Assert.Equal("MyFile", queryParams["dn"]);
|
||||||
}
|
}
|
||||||
|
|
@ -239,7 +240,7 @@ public class EnricherTest : IDisposable
|
||||||
var result = await enricher.EnrichMagnetLink(magnetLink);
|
var result = await enricher.EnrichMagnetLink(magnetLink);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var queryParams = System.Web.HttpUtility.ParseQueryString(new Uri(result).Query);
|
var queryParams = HttpUtility.ParseQueryString(new Uri(result).Query);
|
||||||
var trValues = queryParams.GetValues("tr")?.ToList() ?? new List<String>();
|
var trValues = queryParams.GetValues("tr")?.ToList() ?? new List<String>();
|
||||||
|
|
||||||
Assert.Contains("udp://existing", trValues);
|
Assert.Contains("udp://existing", trValues);
|
||||||
|
|
|
||||||
|
|
@ -522,8 +522,8 @@ public class AllDebridTorrentClientTest
|
||||||
{
|
{
|
||||||
public readonly Mock<IAllDebridNetClientFactory> AllDebridClientFactoryMock;
|
public readonly Mock<IAllDebridNetClientFactory> AllDebridClientFactoryMock;
|
||||||
public readonly Mock<IAllDebridNETClient> AllDebridClientMock;
|
public readonly Mock<IAllDebridNETClient> AllDebridClientMock;
|
||||||
public readonly Mock<ILogger<AllDebridTorrentClient>> LoggerMock;
|
|
||||||
public readonly Mock<IDownloadableFileFilter> FileFilterMock;
|
public readonly Mock<IDownloadableFileFilter> FileFilterMock;
|
||||||
|
public readonly Mock<ILogger<AllDebridTorrentClient>> LoggerMock;
|
||||||
|
|
||||||
public Mocks()
|
public Mocks()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -13,14 +13,14 @@ using TorrentsService = RdtClient.Service.Services.Torrents;
|
||||||
|
|
||||||
namespace RdtClient.Service.Test.Services;
|
namespace RdtClient.Service.Test.Services;
|
||||||
|
|
||||||
class Mocks
|
internal class Mocks
|
||||||
{
|
{
|
||||||
|
public readonly Mock<IDownloads> DownloadsMock;
|
||||||
|
public readonly Mock<IEnricher> EnricherMock;
|
||||||
public readonly Mock<IProcessFactory> ProcessFactoryMock;
|
public readonly Mock<IProcessFactory> ProcessFactoryMock;
|
||||||
public readonly Mock<IProcess> ProcessMock;
|
public readonly Mock<IProcess> ProcessMock;
|
||||||
public readonly Mock<ILogger<TorrentsService>> TorrentsLoggerMock;
|
|
||||||
public readonly Mock<IDownloads> DownloadsMock;
|
|
||||||
public readonly Mock<ITorrentData> TorrentDataMock;
|
public readonly Mock<ITorrentData> TorrentDataMock;
|
||||||
public readonly Mock<IEnricher> EnricherMock;
|
public readonly Mock<ILogger<TorrentsService>> TorrentsLoggerMock;
|
||||||
|
|
||||||
public Mocks()
|
public Mocks()
|
||||||
{
|
{
|
||||||
|
|
@ -63,8 +63,7 @@ public class TorrentsTest
|
||||||
return new()
|
return new()
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
torrent,
|
torrent, downloads
|
||||||
downloads
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -96,7 +95,7 @@ public class TorrentsTest
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
filePath, new("Test file")
|
filePath, new("Test file")
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var torrents = new TorrentsService(mocks.TorrentsLoggerMock.Object,
|
var torrents = new TorrentsService(mocks.TorrentsLoggerMock.Object,
|
||||||
|
|
@ -162,7 +161,7 @@ public class TorrentsTest
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
filePath, new("Test file")
|
filePath, new("Test file")
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var torrents = new TorrentsService(mocks.TorrentsLoggerMock.Object,
|
var torrents = new TorrentsService(mocks.TorrentsLoggerMock.Object,
|
||||||
|
|
@ -210,7 +209,7 @@ public class TorrentsTest
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
filePath, new("Test file")
|
filePath, new("Test file")
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var torrents = new TorrentsService(mocks.TorrentsLoggerMock.Object,
|
var torrents = new TorrentsService(mocks.TorrentsLoggerMock.Object,
|
||||||
|
|
@ -277,7 +276,7 @@ public class TorrentsTest
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
filePath, new("Test file")
|
filePath, new("Test file")
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var torrents = new TorrentsService(mocks.TorrentsLoggerMock.Object,
|
var torrents = new TorrentsService(mocks.TorrentsLoggerMock.Object,
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ namespace RdtClient.Service.BackgroundServices;
|
||||||
|
|
||||||
public class DiskSpaceMonitor(ILogger<DiskSpaceMonitor> logger, IServiceProvider serviceProvider) : BackgroundService
|
public class DiskSpaceMonitor(ILogger<DiskSpaceMonitor> logger, IServiceProvider serviceProvider) : BackgroundService
|
||||||
{
|
{
|
||||||
private Boolean _isPausedForLowDiskSpace;
|
|
||||||
private static DiskSpaceStatus? _lastStatus;
|
private static DiskSpaceStatus? _lastStatus;
|
||||||
|
private Boolean _isPausedForLowDiskSpace;
|
||||||
|
|
||||||
public static DiskSpaceStatus? GetCurrentStatus()
|
public static DiskSpaceStatus? GetCurrentStatus()
|
||||||
{
|
{
|
||||||
|
|
@ -39,10 +39,12 @@ public class DiskSpaceMonitor(ILogger<DiskSpaceMonitor> logger, IServiceProvider
|
||||||
if (minimumFreeSpaceGB <= 0)
|
if (minimumFreeSpaceGB <= 0)
|
||||||
{
|
{
|
||||||
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
|
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var intervalMinutes = Settings.Get.DownloadClient.DiskSpaceCheckIntervalMinutes;
|
var intervalMinutes = Settings.Get.DownloadClient.DiskSpaceCheckIntervalMinutes;
|
||||||
|
|
||||||
if (intervalMinutes < 1)
|
if (intervalMinutes < 1)
|
||||||
{
|
{
|
||||||
intervalMinutes = 1;
|
intervalMinutes = 1;
|
||||||
|
|
@ -50,30 +52,32 @@ public class DiskSpaceMonitor(ILogger<DiskSpaceMonitor> logger, IServiceProvider
|
||||||
|
|
||||||
var downloadPath = Settings.Get.DownloadClient.DownloadPath;
|
var downloadPath = Settings.Get.DownloadClient.DownloadPath;
|
||||||
logger.LogDebug($"Checking disk space for path: {downloadPath}");
|
logger.LogDebug($"Checking disk space for path: {downloadPath}");
|
||||||
|
|
||||||
if (!Directory.Exists(downloadPath))
|
if (!Directory.Exists(downloadPath))
|
||||||
{
|
{
|
||||||
logger.LogWarning($"Download path does not exist: {downloadPath}");
|
logger.LogWarning($"Download path does not exist: {downloadPath}");
|
||||||
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
|
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var availableSpaceGB = FileHelper.GetAvailableFreeSpaceGB(downloadPath);
|
var availableSpaceGB = FileHelper.GetAvailableFreeSpaceGB(downloadPath);
|
||||||
logger.LogDebug($"Disk space check: {availableSpaceGB} GB available (threshold: {minimumFreeSpaceGB} GB, resume: {minimumFreeSpaceGB * 2} GB, isPaused: {_isPausedForLowDiskSpace})");
|
logger.LogDebug($"Disk space check: {availableSpaceGB} GB available (threshold: {minimumFreeSpaceGB} GB, resume: {minimumFreeSpaceGB * 2} GB, isPaused: {_isPausedForLowDiskSpace})");
|
||||||
|
|
||||||
if (availableSpaceGB == 0)
|
if (availableSpaceGB == 0)
|
||||||
{
|
{
|
||||||
logger.LogWarning($"Failed to get disk space for path: {downloadPath}");
|
logger.LogWarning($"Failed to get disk space for path: {downloadPath}");
|
||||||
}
|
}
|
||||||
|
|
||||||
var shouldPause = availableSpaceGB > 0 && availableSpaceGB < minimumFreeSpaceGB;
|
var shouldPause = availableSpaceGB > 0 && availableSpaceGB < minimumFreeSpaceGB;
|
||||||
var shouldResume = availableSpaceGB >= (minimumFreeSpaceGB * 2);
|
var shouldResume = availableSpaceGB >= minimumFreeSpaceGB * 2;
|
||||||
|
|
||||||
if (shouldPause && !_isPausedForLowDiskSpace)
|
if (shouldPause && !_isPausedForLowDiskSpace)
|
||||||
{
|
{
|
||||||
logger.LogWarning($"Pausing Bezzad downloads: {availableSpaceGB} GB available, threshold is {minimumFreeSpaceGB} GB");
|
logger.LogWarning($"Pausing Bezzad downloads: {availableSpaceGB} GB available, threshold is {minimumFreeSpaceGB} GB");
|
||||||
|
|
||||||
var pausedCount = 0;
|
var pausedCount = 0;
|
||||||
|
|
||||||
foreach (var download in TorrentRunner.ActiveDownloadClients)
|
foreach (var download in TorrentRunner.ActiveDownloadClients)
|
||||||
{
|
{
|
||||||
if (download.Value.Type == DownloadClient.Bezzad)
|
if (download.Value.Type == DownloadClient.Bezzad)
|
||||||
|
|
@ -83,7 +87,7 @@ public class DiskSpaceMonitor(ILogger<DiskSpaceMonitor> logger, IServiceProvider
|
||||||
pausedCount++;
|
pausedCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.LogInformation($"Paused {pausedCount} active Bezzad downloads");
|
logger.LogInformation($"Paused {pausedCount} active Bezzad downloads");
|
||||||
|
|
||||||
TorrentRunner.IsPausedForLowDiskSpace = true;
|
TorrentRunner.IsPausedForLowDiskSpace = true;
|
||||||
|
|
@ -96,13 +100,14 @@ public class DiskSpaceMonitor(ILogger<DiskSpaceMonitor> logger, IServiceProvider
|
||||||
ThresholdGB = minimumFreeSpaceGB,
|
ThresholdGB = minimumFreeSpaceGB,
|
||||||
LastCheckTime = DateTimeOffset.UtcNow
|
LastCheckTime = DateTimeOffset.UtcNow
|
||||||
};
|
};
|
||||||
|
|
||||||
_lastStatus = status;
|
_lastStatus = status;
|
||||||
await remoteService.UpdateDiskSpaceStatus(status);
|
await remoteService.UpdateDiskSpaceStatus(status);
|
||||||
}
|
}
|
||||||
else if (shouldPause && _isPausedForLowDiskSpace)
|
else if (shouldPause && _isPausedForLowDiskSpace)
|
||||||
{
|
{
|
||||||
logger.LogDebug($"Still paused: {availableSpaceGB} GB available (need {minimumFreeSpaceGB * 2} GB to resume)");
|
logger.LogDebug($"Still paused: {availableSpaceGB} GB available (need {minimumFreeSpaceGB * 2} GB to resume)");
|
||||||
|
|
||||||
var status = new DiskSpaceStatus
|
var status = new DiskSpaceStatus
|
||||||
{
|
{
|
||||||
IsPaused = true,
|
IsPaused = true,
|
||||||
|
|
@ -110,6 +115,7 @@ public class DiskSpaceMonitor(ILogger<DiskSpaceMonitor> logger, IServiceProvider
|
||||||
ThresholdGB = minimumFreeSpaceGB,
|
ThresholdGB = minimumFreeSpaceGB,
|
||||||
LastCheckTime = DateTimeOffset.UtcNow
|
LastCheckTime = DateTimeOffset.UtcNow
|
||||||
};
|
};
|
||||||
|
|
||||||
_lastStatus = status;
|
_lastStatus = status;
|
||||||
await remoteService.UpdateDiskSpaceStatus(status);
|
await remoteService.UpdateDiskSpaceStatus(status);
|
||||||
}
|
}
|
||||||
|
|
@ -118,6 +124,7 @@ public class DiskSpaceMonitor(ILogger<DiskSpaceMonitor> logger, IServiceProvider
|
||||||
logger.LogInformation($"Resuming Bezzad downloads: {availableSpaceGB} GB available, resume threshold is {minimumFreeSpaceGB * 2} GB");
|
logger.LogInformation($"Resuming Bezzad downloads: {availableSpaceGB} GB available, resume threshold is {minimumFreeSpaceGB * 2} GB");
|
||||||
|
|
||||||
var resumedCount = 0;
|
var resumedCount = 0;
|
||||||
|
|
||||||
foreach (var download in TorrentRunner.ActiveDownloadClients)
|
foreach (var download in TorrentRunner.ActiveDownloadClients)
|
||||||
{
|
{
|
||||||
if (download.Value.Type == DownloadClient.Bezzad)
|
if (download.Value.Type == DownloadClient.Bezzad)
|
||||||
|
|
@ -127,7 +134,7 @@ public class DiskSpaceMonitor(ILogger<DiskSpaceMonitor> logger, IServiceProvider
|
||||||
resumedCount++;
|
resumedCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.LogInformation($"Resumed {resumedCount} Bezzad downloads");
|
logger.LogInformation($"Resumed {resumedCount} Bezzad downloads");
|
||||||
|
|
||||||
TorrentRunner.IsPausedForLowDiskSpace = false;
|
TorrentRunner.IsPausedForLowDiskSpace = false;
|
||||||
|
|
@ -140,10 +147,11 @@ public class DiskSpaceMonitor(ILogger<DiskSpaceMonitor> logger, IServiceProvider
|
||||||
ThresholdGB = minimumFreeSpaceGB,
|
ThresholdGB = minimumFreeSpaceGB,
|
||||||
LastCheckTime = DateTimeOffset.UtcNow
|
LastCheckTime = DateTimeOffset.UtcNow
|
||||||
};
|
};
|
||||||
|
|
||||||
_lastStatus = status;
|
_lastStatus = status;
|
||||||
await remoteService.UpdateDiskSpaceStatus(status);
|
await remoteService.UpdateDiskSpaceStatus(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.Delay(TimeSpan.FromMinutes(intervalMinutes), stoppingToken);
|
await Task.Delay(TimeSpan.FromMinutes(intervalMinutes), stoppingToken);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ public class ProviderUpdater(ILogger<ProviderUpdater> logger, IServiceProvider s
|
||||||
|
|
||||||
using var scope = serviceProvider.CreateScope();
|
using var scope = serviceProvider.CreateScope();
|
||||||
var torrentService = scope.ServiceProvider.GetRequiredService<Torrents>();
|
var torrentService = scope.ServiceProvider.GetRequiredService<Torrents>();
|
||||||
|
|
||||||
logger.LogInformation("ProviderUpdater started.");
|
logger.LogInformation("ProviderUpdater started.");
|
||||||
|
|
||||||
while (!stoppingToken.IsCancellationRequested)
|
while (!stoppingToken.IsCancellationRequested)
|
||||||
|
|
@ -31,7 +31,7 @@ public class ProviderUpdater(ILogger<ProviderUpdater> logger, IServiceProvider s
|
||||||
if (_nextUpdate < DateTime.UtcNow && (Settings.Get.Provider.AutoImport || torrents.Any(t => t.RdStatus != TorrentStatus.Finished)))
|
if (_nextUpdate < DateTime.UtcNow && (Settings.Get.Provider.AutoImport || torrents.Any(t => t.RdStatus != TorrentStatus.Finished)))
|
||||||
{
|
{
|
||||||
logger.LogDebug($"Updating torrent info from debrid provider");
|
logger.LogDebug($"Updating torrent info from debrid provider");
|
||||||
|
|
||||||
var updateTime = Settings.Get.Provider.CheckInterval * 3;
|
var updateTime = Settings.Get.Provider.CheckInterval * 3;
|
||||||
|
|
||||||
if (updateTime < 30)
|
if (updateTime < 30)
|
||||||
|
|
@ -66,4 +66,4 @@ public class ProviderUpdater(ILogger<ProviderUpdater> logger, IServiceProvider s
|
||||||
|
|
||||||
logger.LogInformation("ProviderUpdater stopped.");
|
logger.LogInformation("ProviderUpdater stopped.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ using Microsoft.Extensions.Logging;
|
||||||
using RdtClient.Data.Data;
|
using RdtClient.Data.Data;
|
||||||
using RdtClient.Service.Services;
|
using RdtClient.Service.Services;
|
||||||
|
|
||||||
|
|
||||||
namespace RdtClient.Service.BackgroundServices;
|
namespace RdtClient.Service.BackgroundServices;
|
||||||
|
|
||||||
public class Startup(IServiceProvider serviceProvider) : IHostedService
|
public class Startup(IServiceProvider serviceProvider) : IHostedService
|
||||||
|
|
@ -16,7 +15,7 @@ public class Startup(IServiceProvider serviceProvider) : IHostedService
|
||||||
public async Task StartAsync(CancellationToken cancellationToken)
|
public async Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var version = Assembly.GetEntryAssembly()?.GetName().Version;
|
var version = Assembly.GetEntryAssembly()?.GetName().Version;
|
||||||
|
|
||||||
using var scope = serviceProvider.CreateScope();
|
using var scope = serviceProvider.CreateScope();
|
||||||
var logger = scope.ServiceProvider.GetRequiredService<ILogger<Startup>>();
|
var logger = scope.ServiceProvider.GetRequiredService<ILogger<Startup>>();
|
||||||
|
|
||||||
|
|
@ -32,5 +31,8 @@ public class Startup(IServiceProvider serviceProvider) : IHostedService
|
||||||
Ready = true;
|
Ready = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
}
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,11 @@ public class TaskRunner(ILogger<TaskRunner> logger, IServiceProvider serviceProv
|
||||||
|
|
||||||
using var scope = serviceProvider.CreateScope();
|
using var scope = serviceProvider.CreateScope();
|
||||||
var torrentRunner = scope.ServiceProvider.GetRequiredService<TorrentRunner>();
|
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
|
||||||
|
|
@ -60,4 +60,4 @@ public class TaskRunner(ILogger<TaskRunner> logger, IServiceProvider serviceProv
|
||||||
|
|
||||||
logger.LogInformation("TaskRunner stopped.");
|
logger.LogInformation("TaskRunner stopped.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,11 @@ namespace RdtClient.Service.BackgroundServices;
|
||||||
|
|
||||||
public class UpdateChecker(ILogger<UpdateChecker> logger) : BackgroundService
|
public class UpdateChecker(ILogger<UpdateChecker> logger) : BackgroundService
|
||||||
{
|
{
|
||||||
|
private static readonly List<String> KnownGhsaIds = [];
|
||||||
public static String? CurrentVersion { get; private set; }
|
public static String? CurrentVersion { get; private set; }
|
||||||
public static String? LatestVersion { get; private set; }
|
public static String? LatestVersion { get; private set; }
|
||||||
|
|
||||||
public static Boolean? IsInsecure { get; private set; }
|
|
||||||
|
|
||||||
private static readonly List<String> KnownGhsaIds = [];
|
public static Boolean? IsInsecure { get; private set; }
|
||||||
|
|
||||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
{
|
{
|
||||||
|
|
@ -45,6 +44,7 @@ public class UpdateChecker(ILogger<UpdateChecker> logger) : BackgroundService
|
||||||
if (latestRelease == null)
|
if (latestRelease == null)
|
||||||
{
|
{
|
||||||
logger.LogWarning($"Unable to find latest version on GitHub");
|
logger.LogWarning($"Unable to find latest version on GitHub");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,10 +58,11 @@ public class UpdateChecker(ILogger<UpdateChecker> logger) : BackgroundService
|
||||||
var gitHubSecurityAdvisories = await GitHubRequest<List<GitHubSecurityAdvisoriesResponse>>("/repos/rogerfar/rdt-client/security-advisories", stoppingToken);
|
var gitHubSecurityAdvisories = await GitHubRequest<List<GitHubSecurityAdvisoriesResponse>>("/repos/rogerfar/rdt-client/security-advisories", stoppingToken);
|
||||||
|
|
||||||
var unseenGhsaIds = gitHubSecurityAdvisories?.Where(advisory => !KnownGhsaIds.Contains(advisory.GhsaId));
|
var unseenGhsaIds = gitHubSecurityAdvisories?.Where(advisory => !KnownGhsaIds.Contains(advisory.GhsaId));
|
||||||
|
|
||||||
if (unseenGhsaIds == null)
|
if (unseenGhsaIds == null)
|
||||||
{
|
{
|
||||||
logger.LogWarning($"Unable to find security advisories on GitHub");
|
logger.LogWarning($"Unable to find security advisories on GitHub");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,15 +81,15 @@ public class UpdateChecker(ILogger<UpdateChecker> logger) : BackgroundService
|
||||||
|
|
||||||
private static async Task<T?> GitHubRequest<T>(String endpoint, CancellationToken cancellationToken)
|
private static async Task<T?> GitHubRequest<T>(String endpoint, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var httpClient = new HttpClient();
|
var httpClient = new HttpClient();
|
||||||
httpClient.DefaultRequestHeaders.UserAgent.Add(new("RdtClient", CurrentVersion));
|
httpClient.DefaultRequestHeaders.UserAgent.Add(new("RdtClient", CurrentVersion));
|
||||||
var response = await httpClient.GetStringAsync($"https://api.github.com{endpoint}", cancellationToken);
|
var response = await httpClient.GetStringAsync($"https://api.github.com{endpoint}", cancellationToken);
|
||||||
|
|
||||||
return JsonConvert.DeserializeObject<T>(response);
|
return JsonConvert.DeserializeObject<T>(response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GitHubReleasesResponse
|
public class GitHubReleasesResponse
|
||||||
{
|
{
|
||||||
[JsonProperty("name")]
|
[JsonProperty("name")]
|
||||||
public String? Name { get; set; }
|
public String? Name { get; set; }
|
||||||
|
|
@ -97,5 +98,5 @@ public class GitHubReleasesResponse
|
||||||
public class GitHubSecurityAdvisoriesResponse
|
public class GitHubSecurityAdvisoriesResponse
|
||||||
{
|
{
|
||||||
[JsonProperty("ghsa_id")]
|
[JsonProperty("ghsa_id")]
|
||||||
public required String GhsaId { get; set; }
|
public required String GhsaId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ public class WatchFolderChecker(ILogger<WatchFolderChecker> logger, IServiceProv
|
||||||
|
|
||||||
using var scope = serviceProvider.CreateScope();
|
using var scope = serviceProvider.CreateScope();
|
||||||
var torrentService = scope.ServiceProvider.GetRequiredService<Torrents>();
|
var torrentService = scope.ServiceProvider.GetRequiredService<Torrents>();
|
||||||
|
|
||||||
logger.LogInformation("WatchFolderChecker started.");
|
logger.LogInformation("WatchFolderChecker started.");
|
||||||
|
|
||||||
while (!stoppingToken.IsCancellationRequested)
|
while (!stoppingToken.IsCancellationRequested)
|
||||||
|
|
@ -112,7 +112,7 @@ public class WatchFolderChecker(ILogger<WatchFolderChecker> logger, IServiceProv
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(processedStorePath);
|
Directory.CreateDirectory(processedStorePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
var processedPath = Path.Combine(processedStorePath, fileInfo.Name);
|
var processedPath = Path.Combine(processedStorePath, fileInfo.Name);
|
||||||
|
|
||||||
if (File.Exists(processedPath))
|
if (File.Exists(processedPath))
|
||||||
|
|
@ -147,6 +147,7 @@ public class WatchFolderChecker(ILogger<WatchFolderChecker> logger, IServiceProv
|
||||||
fileInfo.Name,
|
fileInfo.Name,
|
||||||
errorStorePath);
|
errorStorePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
File.Move(torrentFile, processedPath);
|
File.Move(torrentFile, processedPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -169,6 +170,7 @@ public class WatchFolderChecker(ILogger<WatchFolderChecker> logger, IServiceProv
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,4 +34,4 @@ public class WebsocketsUpdater(ILogger<WebsocketsUpdater> logger, IServiceProvid
|
||||||
|
|
||||||
logger.LogInformation("WebsocketsUpdater stopped.");
|
logger.LogInformation("WebsocketsUpdater stopped.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,9 +61,10 @@ public static class DiConfig
|
||||||
var retryPolicy = HttpPolicyExtensions
|
var retryPolicy = HttpPolicyExtensions
|
||||||
.HandleTransientHttpError()
|
.HandleTransientHttpError()
|
||||||
.OrResult(r => r.StatusCode == HttpStatusCode.TooManyRequests)
|
.OrResult(r => r.StatusCode == HttpStatusCode.TooManyRequests)
|
||||||
.WaitAndRetryAsync(retryCount: 5, sleepDurationProvider: attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)));
|
.WaitAndRetryAsync(5, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)));
|
||||||
|
|
||||||
services.AddHttpClient();
|
services.AddHttpClient();
|
||||||
|
|
||||||
services.ConfigureHttpClientDefaults(builder =>
|
services.ConfigureHttpClientDefaults(builder =>
|
||||||
{
|
{
|
||||||
builder.ConfigureHttpClient(httpClient =>
|
builder.ConfigureHttpClient(httpClient =>
|
||||||
|
|
|
||||||
|
|
@ -5,5 +5,6 @@
|
||||||
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
[assembly: SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "Bug in Serilog", Scope = "NamespaceAndDescendants", Target = "N:RdtClient.Service")]
|
[assembly:
|
||||||
[assembly: SuppressMessage("Style", "IDE0305:Simplify collection initialization", Justification = "Code style", Scope = "NamespaceAndDescendants", Target = "N:RdtClient.Service")]
|
SuppressMessage("Usage", "CA2254:Template should be a static expression", Justification = "Bug in Serilog", Scope = "NamespaceAndDescendants", Target = "N:RdtClient.Service")]
|
||||||
|
[assembly: SuppressMessage("Style", "IDE0305:Simplify collection initialization", Justification = "Code style", Scope = "NamespaceAndDescendants", Target = "N:RdtClient.Service")]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using System.IO.Abstractions;
|
using System.IO.Abstractions;
|
||||||
using RdtClient.Data.Models.Data;
|
|
||||||
using System.Web;
|
using System.Web;
|
||||||
|
using RdtClient.Data.Models.Data;
|
||||||
|
|
||||||
namespace RdtClient.Service.Helpers;
|
namespace RdtClient.Service.Helpers;
|
||||||
|
|
||||||
|
|
@ -16,7 +16,7 @@ public static class DownloadHelper
|
||||||
}
|
}
|
||||||
|
|
||||||
var directory = RemoveInvalidPathChars(torrent.RdName);
|
var directory = RemoveInvalidPathChars(torrent.RdName);
|
||||||
|
|
||||||
var torrentPath = Path.Combine(downloadPath, directory);
|
var torrentPath = Path.Combine(downloadPath, directory);
|
||||||
|
|
||||||
var fileName = GetFileName(download);
|
var fileName = GetFileName(download);
|
||||||
|
|
|
||||||
|
|
@ -80,11 +80,12 @@ public static class FileHelper
|
||||||
{
|
{
|
||||||
return String.Concat(filename.Split(Path.GetInvalidFileNameChars()));
|
return String.Concat(filename.Split(Path.GetInvalidFileNameChars()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String GetDirectoryContents(String path)
|
public static String GetDirectoryContents(String path)
|
||||||
{
|
{
|
||||||
var stringBuilder = new StringBuilder();
|
var stringBuilder = new StringBuilder();
|
||||||
GetDirectoryContents(path, stringBuilder, "");
|
GetDirectoryContents(path, stringBuilder, "");
|
||||||
|
|
||||||
return stringBuilder.ToString();
|
return stringBuilder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,6 +94,7 @@ public static class FileHelper
|
||||||
var directoryInfo = new DirectoryInfo(path);
|
var directoryInfo = new DirectoryInfo(path);
|
||||||
|
|
||||||
var directories = directoryInfo.GetDirectories();
|
var directories = directoryInfo.GetDirectories();
|
||||||
|
|
||||||
foreach (var directory in directories)
|
foreach (var directory in directories)
|
||||||
{
|
{
|
||||||
stringBuilder.AppendLine($"{indent}{directory.Name}");
|
stringBuilder.AppendLine($"{indent}{directory.Name}");
|
||||||
|
|
@ -100,6 +102,7 @@ public static class FileHelper
|
||||||
}
|
}
|
||||||
|
|
||||||
var files = directoryInfo.GetFiles();
|
var files = directoryInfo.GetFiles();
|
||||||
|
|
||||||
foreach (var file in files)
|
foreach (var file in files)
|
||||||
{
|
{
|
||||||
stringBuilder.AppendLine($"{indent}{file.Name}");
|
stringBuilder.AppendLine($"{indent}{file.Name}");
|
||||||
|
|
@ -112,13 +115,16 @@ public static class FileHelper
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!Directory.Exists(path))
|
if (!Directory.Exists(path))
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
var driveInfo = new DriveInfo(path);
|
var driveInfo = new DriveInfo(path);
|
||||||
|
|
||||||
return driveInfo.AvailableFreeSpace / (1024 * 1024 * 1024);
|
return driveInfo.AvailableFreeSpace / (1024 * 1024 * 1024);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
|
@ -126,4 +132,4 @@ public static class FileHelper
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace RdtClient.Service.Helpers;
|
namespace RdtClient.Service.Helpers;
|
||||||
|
|
||||||
|
|
@ -9,19 +10,22 @@ public class JsonModelBinder : IModelBinder
|
||||||
ArgumentNullException.ThrowIfNull(bindingContext);
|
ArgumentNullException.ThrowIfNull(bindingContext);
|
||||||
|
|
||||||
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
|
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
|
||||||
|
|
||||||
if (valueProviderResult != ValueProviderResult.None)
|
if (valueProviderResult != ValueProviderResult.None)
|
||||||
{
|
{
|
||||||
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
|
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
|
||||||
|
|
||||||
var valueAsString = valueProviderResult.FirstValue ?? "";
|
var valueAsString = valueProviderResult.FirstValue ?? "";
|
||||||
var result = Newtonsoft.Json.JsonConvert.DeserializeObject(valueAsString, bindingContext.ModelType);
|
var result = JsonConvert.DeserializeObject(valueAsString, bindingContext.ModelType);
|
||||||
|
|
||||||
if (result != null)
|
if (result != null)
|
||||||
{
|
{
|
||||||
bindingContext.Result = ModelBindingResult.Success(result);
|
bindingContext.Result = ModelBindingResult.Success(result);
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ public static class Logger
|
||||||
fileName = HttpUtility.UrlDecode(fileName);
|
fileName = HttpUtility.UrlDecode(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
var done = (Int32)((Double)download.BytesDone / download.BytesTotal * 100);
|
var done = (Int32)(((Double)download.BytesDone / download.BytesTotal) * 100);
|
||||||
|
|
||||||
if (done < 0)
|
if (done < 0)
|
||||||
{
|
{
|
||||||
|
|
@ -30,4 +30,4 @@ public static class Logger
|
||||||
{
|
{
|
||||||
return $"for torrent {torrent.RdName} ({torrent.RdId} - {torrent.RdStatusRaw} {torrent.RdProgress}%) ({torrent.TorrentId})";
|
return $"for torrent {torrent.RdName} ({torrent.RdId} - {torrent.RdStatusRaw} {torrent.RdProgress}%) ({torrent.TorrentId})";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,11 @@ namespace RdtClient.Service.Middleware;
|
||||||
|
|
||||||
public class AuthSettingRequirement : IAuthorizationRequirement
|
public class AuthSettingRequirement : IAuthorizationRequirement
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AuthSettingHandler : AuthorizationHandler<AuthSettingRequirement>
|
public class AuthSettingHandler : AuthorizationHandler<AuthSettingRequirement>
|
||||||
{
|
{
|
||||||
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AuthSettingRequirement requirement)
|
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AuthSettingRequirement requirement)
|
||||||
{
|
{
|
||||||
if (context.User.Identity?.IsAuthenticated == true)
|
if (context.User.Identity?.IsAuthenticated == true)
|
||||||
{
|
{
|
||||||
|
|
@ -23,6 +22,6 @@ public class AuthSettingHandler : AuthorizationHandler<AuthSettingRequirement>
|
||||||
context.Succeed(requirement);
|
context.Succeed(requirement);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ namespace RdtClient.Service.Middleware;
|
||||||
public class AuthorizeMiddleware(RequestDelegate next)
|
public class AuthorizeMiddleware(RequestDelegate next)
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Return a 403 instead of a 401, it's quirk that QBittorrent has.
|
/// Return a 403 instead of a 401, it's quirk that QBittorrent has.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context"></param>
|
/// <param name="context"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
|
@ -14,9 +14,9 @@ public class AuthorizeMiddleware(RequestDelegate next)
|
||||||
{
|
{
|
||||||
await next(context);
|
await next(context);
|
||||||
|
|
||||||
if (context.Response.StatusCode == (Int32) HttpStatusCode.Unauthorized)
|
if (context.Response.StatusCode == (Int32)HttpStatusCode.Unauthorized)
|
||||||
{
|
{
|
||||||
context.Response.StatusCode = (Int32) HttpStatusCode.Forbidden;
|
context.Response.StatusCode = (Int32)HttpStatusCode.Forbidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ namespace RdtClient.Service.Middleware;
|
||||||
|
|
||||||
public partial class BaseHrefMiddleware(RequestDelegate next, String basePath)
|
public partial class BaseHrefMiddleware(RequestDelegate next, String basePath)
|
||||||
{
|
{
|
||||||
|
private readonly String _basePath = $"/{basePath.TrimStart('/').TrimEnd('/')}/";
|
||||||
|
|
||||||
[GeneratedRegex(@"<base href=""/""")]
|
[GeneratedRegex(@"<base href=""/""")]
|
||||||
private partial Regex BodyRegex();
|
private partial Regex BodyRegex();
|
||||||
|
|
||||||
|
|
@ -14,8 +16,6 @@ public partial class BaseHrefMiddleware(RequestDelegate next, String basePath)
|
||||||
[GeneratedRegex("(<link.*?href=\")(.*?)(\".*?>)")]
|
[GeneratedRegex("(<link.*?href=\")(.*?)(\".*?>)")]
|
||||||
private partial Regex LinkRegex();
|
private partial Regex LinkRegex();
|
||||||
|
|
||||||
private readonly String _basePath = $"/{basePath.TrimStart('/').TrimEnd('/')}/";
|
|
||||||
|
|
||||||
public async Task InvokeAsync(HttpContext context)
|
public async Task InvokeAsync(HttpContext context)
|
||||||
{
|
{
|
||||||
var originalBody = context.Response.Body;
|
var originalBody = context.Response.Body;
|
||||||
|
|
@ -55,4 +55,4 @@ public partial class BaseHrefMiddleware(RequestDelegate next, String basePath)
|
||||||
context.Response.Body = originalBody;
|
context.Response.Body = originalBody;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,11 @@ public static class ExceptionMiddlewareExtensions
|
||||||
{
|
{
|
||||||
appError.Run(async context =>
|
appError.Run(async context =>
|
||||||
{
|
{
|
||||||
context.Response.StatusCode = (Int32) HttpStatusCode.InternalServerError;
|
context.Response.StatusCode = (Int32)HttpStatusCode.InternalServerError;
|
||||||
context.Response.ContentType = "application/json";
|
context.Response.ContentType = "application/json";
|
||||||
|
|
||||||
var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
|
var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
|
||||||
|
|
||||||
if (contextFeature != null)
|
if (contextFeature != null)
|
||||||
{
|
{
|
||||||
await context.Response.WriteAsync(contextFeature.Error.Message);
|
await context.Response.WriteAsync(contextFeature.Error.Message);
|
||||||
|
|
@ -24,4 +25,4 @@ public static class ExceptionMiddlewareExtensions
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using Microsoft.AspNetCore.Http;
|
using System.Text;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace RdtClient.Service.Middleware;
|
namespace RdtClient.Service.Middleware;
|
||||||
|
|
||||||
|
|
@ -43,7 +43,7 @@ public class RequestLoggingMiddleware(RequestDelegate next, ILoggerFactory logge
|
||||||
{
|
{
|
||||||
request.EnableBuffering();
|
request.EnableBuffering();
|
||||||
|
|
||||||
using var reader = new StreamReader(request.Body, Encoding.UTF8, detectEncodingFromByteOrderMarks: false, leaveOpen: true);
|
using var reader = new StreamReader(request.Body, Encoding.UTF8, false, leaveOpen: true);
|
||||||
var body = await reader.ReadToEndAsync();
|
var body = await reader.ReadToEndAsync();
|
||||||
|
|
||||||
request.Body.Position = 0;
|
request.Body.Position = 0;
|
||||||
|
|
|
||||||
|
|
@ -57,4 +57,4 @@ public class Authentication(SignInManager<IdentityUser> signInManager, UserManag
|
||||||
|
|
||||||
return IdentityResult.Success;
|
return IdentityResult.Success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,7 @@ public class DownloadClient(Download download, Torrent torrent, String destinati
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await Downloader.Cancel();
|
await Downloader.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -126,6 +127,7 @@ public class DownloadClient(Download download, Torrent torrent, String destinati
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await Downloader.Pause();
|
await Downloader.Pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,6 +137,7 @@ public class DownloadClient(Download download, Torrent torrent, String destinati
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await Downloader.Resume();
|
await Downloader.Resume();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -153,4 +156,4 @@ public class DownloadClient(Download download, Torrent torrent, String destinati
|
||||||
_totalBytesDownloadedThisSession += bytes;
|
_totalBytesDownloadedThisSession += bytes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ public class DownloadableFileFilter(ILogger<DownloadableFileFilter> logger) : ID
|
||||||
{
|
{
|
||||||
logger.LogDebug("File {filePath} was included after filtering", filePath);
|
logger.LogDebug("File {filePath} was included after filtering", filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
return isDownloadable;
|
return isDownloadable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,17 +5,14 @@ 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 const Int32 RetryCount = 5;
|
||||||
|
|
||||||
private readonly Aria2NetClient _aria2NetClient;
|
private readonly Aria2NetClient _aria2NetClient;
|
||||||
|
private readonly String _filePath;
|
||||||
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly String _uri;
|
|
||||||
private readonly String _filePath;
|
|
||||||
private readonly String _remotePath;
|
private readonly String _remotePath;
|
||||||
|
private readonly String _uri;
|
||||||
|
|
||||||
private String? _gid;
|
private String? _gid;
|
||||||
|
|
||||||
|
|
@ -49,7 +46,10 @@ public class Aria2cDownloader : IDownloader
|
||||||
|
|
||||||
_aria2NetClient = new(Settings.Get.DownloadClient.Aria2cUrl, Settings.Get.DownloadClient.Aria2cSecret, httpClient, 10);
|
_aria2NetClient = new(Settings.Get.DownloadClient.Aria2cUrl, Settings.Get.DownloadClient.Aria2cSecret, httpClient, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public event EventHandler<DownloadCompleteEventArgs>? DownloadComplete;
|
||||||
|
public event EventHandler<DownloadProgressEventArgs>? DownloadProgress;
|
||||||
|
|
||||||
public async Task<String> Download()
|
public async Task<String> Download()
|
||||||
{
|
{
|
||||||
var path = Path.GetDirectoryName(_remotePath) ?? throw new($"Invalid file path {_filePath}");
|
var path = Path.GetDirectoryName(_remotePath) ?? throw new($"Invalid file path {_filePath}");
|
||||||
|
|
@ -68,7 +68,8 @@ public class Aria2cDownloader : IDownloader
|
||||||
}
|
}
|
||||||
|
|
||||||
var retryCount = 0;
|
var retryCount = 0;
|
||||||
while(true)
|
|
||||||
|
while (true)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -85,7 +86,7 @@ public class Aria2cDownloader : IDownloader
|
||||||
_gid = null;
|
_gid = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_gid ??= await _aria2NetClient.AddUriAsync([
|
_gid ??= await _aria2NetClient.AddUriAsync([
|
||||||
_uri
|
_uri
|
||||||
],
|
],
|
||||||
|
|
@ -177,20 +178,25 @@ public class Aria2cDownloader : IDownloader
|
||||||
|
|
||||||
if (download == null)
|
if (download == null)
|
||||||
{
|
{
|
||||||
DownloadComplete?.Invoke(this, new()
|
DownloadComplete?.Invoke(this,
|
||||||
{
|
new()
|
||||||
Error = $"Download was not found in Aria2"
|
{
|
||||||
});
|
Error = $"Download was not found in Aria2"
|
||||||
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!String.IsNullOrWhiteSpace(download.ErrorMessage) || download.Status == "error")
|
if (!String.IsNullOrWhiteSpace(download.ErrorMessage) || download.Status == "error")
|
||||||
{
|
{
|
||||||
await Remove();
|
await Remove();
|
||||||
DownloadComplete?.Invoke(this, new()
|
|
||||||
{
|
DownloadComplete?.Invoke(this,
|
||||||
Error = $"{download.ErrorCode}: {download.ErrorMessage}"
|
new()
|
||||||
});
|
{
|
||||||
|
Error = $"{download.ErrorCode}: {download.ErrorMessage}"
|
||||||
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -201,42 +207,49 @@ public class Aria2cDownloader : IDownloader
|
||||||
await Remove();
|
await Remove();
|
||||||
|
|
||||||
var retryCount = 0;
|
var retryCount = 0;
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
DownloadProgress?.Invoke(this, new()
|
DownloadProgress?.Invoke(this,
|
||||||
{
|
new()
|
||||||
BytesDone = download.CompletedLength,
|
{
|
||||||
BytesTotal = download.TotalLength,
|
BytesDone = download.CompletedLength,
|
||||||
Speed = download.DownloadSpeed
|
BytesTotal = download.TotalLength,
|
||||||
});
|
Speed = download.DownloadSpeed
|
||||||
|
});
|
||||||
|
|
||||||
if (retryCount >= 10)
|
if (retryCount >= 10)
|
||||||
{
|
{
|
||||||
DownloadComplete?.Invoke(this, new()
|
DownloadComplete?.Invoke(this,
|
||||||
{
|
new()
|
||||||
Error = $"File not found at {_filePath} (on aria2 {_remotePath})"
|
{
|
||||||
});
|
Error = $"File not found at {_filePath} (on aria2 {_remotePath})"
|
||||||
|
});
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (File.Exists(_filePath))
|
if (File.Exists(_filePath))
|
||||||
{
|
{
|
||||||
DownloadComplete?.Invoke(this, new());
|
DownloadComplete?.Invoke(this, new());
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.Delay(1000 * retryCount);
|
await Task.Delay(1000 * retryCount);
|
||||||
retryCount++;
|
retryCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DownloadProgress?.Invoke(this, new()
|
DownloadProgress?.Invoke(this,
|
||||||
{
|
new()
|
||||||
BytesDone = download.CompletedLength,
|
{
|
||||||
BytesTotal = download.TotalLength,
|
BytesDone = download.CompletedLength,
|
||||||
Speed = download.DownloadSpeed
|
BytesTotal = download.TotalLength,
|
||||||
});
|
Speed = download.DownloadSpeed
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Remove()
|
private async Task Remove()
|
||||||
|
|
@ -284,4 +297,4 @@ public class Aria2cDownloader : IDownloader
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,16 +6,14 @@ namespace RdtClient.Service.Services.Downloaders;
|
||||||
|
|
||||||
public class BezzadDownloader : IDownloader
|
public class BezzadDownloader : IDownloader
|
||||||
{
|
{
|
||||||
public event EventHandler<DownloadCompleteEventArgs>? DownloadComplete;
|
|
||||||
public event EventHandler<DownloadProgressEventArgs>? DownloadProgress;
|
|
||||||
|
|
||||||
private readonly DownloadService _downloadService;
|
|
||||||
private readonly DownloadConfiguration _downloadConfiguration;
|
private readonly DownloadConfiguration _downloadConfiguration;
|
||||||
|
|
||||||
|
private readonly DownloadService _downloadService;
|
||||||
|
|
||||||
private readonly String _filePath;
|
private readonly String _filePath;
|
||||||
private readonly String _uri;
|
|
||||||
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
private readonly String _uri;
|
||||||
|
|
||||||
private Boolean _finished;
|
private Boolean _finished;
|
||||||
|
|
||||||
|
|
@ -65,12 +63,12 @@ public class BezzadDownloader : IDownloader
|
||||||
}
|
}
|
||||||
|
|
||||||
DownloadProgress.Invoke(this,
|
DownloadProgress.Invoke(this,
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Speed = (Int64)args.BytesPerSecondSpeed,
|
Speed = (Int64)args.BytesPerSecondSpeed,
|
||||||
BytesDone = args.ReceivedBytesSize,
|
BytesDone = args.ReceivedBytesSize,
|
||||||
BytesTotal = args.TotalBytesToReceive
|
BytesTotal = args.TotalBytesToReceive
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
_downloadService.DownloadFileCompleted += (_, args) =>
|
_downloadService.DownloadFileCompleted += (_, args) =>
|
||||||
|
|
@ -96,6 +94,9 @@ public class BezzadDownloader : IDownloader
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public event EventHandler<DownloadCompleteEventArgs>? DownloadComplete;
|
||||||
|
public event EventHandler<DownloadProgressEventArgs>? DownloadProgress;
|
||||||
|
|
||||||
public Task<String> Download()
|
public Task<String> Download()
|
||||||
{
|
{
|
||||||
_logger.Debug($"Starting download of {_uri}, writing to path: {_filePath}");
|
_logger.Debug($"Starting download of {_uri}, writing to path: {_filePath}");
|
||||||
|
|
@ -123,6 +124,7 @@ public class BezzadDownloader : IDownloader
|
||||||
{
|
{
|
||||||
_logger.Debug($"Pausing download {_uri}");
|
_logger.Debug($"Pausing download {_uri}");
|
||||||
_downloadService.Pause();
|
_downloadService.Pause();
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -130,6 +132,7 @@ public class BezzadDownloader : IDownloader
|
||||||
{
|
{
|
||||||
_logger.Debug($"Resuming download {_uri}");
|
_logger.Debug($"Resuming download {_uri}");
|
||||||
_downloadService.Resume();
|
_downloadService.Resume();
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -166,7 +169,7 @@ public class BezzadDownloader : IDownloader
|
||||||
{
|
{
|
||||||
_downloadConfiguration.ChunkCount = Settings.Get.DownloadClient.ChunkCount;
|
_downloadConfiguration.ChunkCount = Settings.Get.DownloadClient.ChunkCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
_downloadConfiguration.MaximumBytesPerSecond = settingDownloadMaxSpeed;
|
_downloadConfiguration.MaximumBytesPerSecond = settingDownloadMaxSpeed;
|
||||||
_downloadConfiguration.ParallelDownload = settingParallelCount > 1;
|
_downloadConfiguration.ParallelDownload = settingParallelCount > 1;
|
||||||
_downloadConfiguration.ParallelCount = settingParallelCount;
|
_downloadConfiguration.ParallelCount = settingParallelCount;
|
||||||
|
|
@ -188,4 +191,4 @@ public class BezzadDownloader : IDownloader
|
||||||
SetSettings();
|
SetSettings();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ using Synology.Api.Client.Apis.DownloadStation.Task.Models;
|
||||||
|
|
||||||
namespace RdtClient.Service.Services.Downloaders;
|
namespace RdtClient.Service.Services.Downloaders;
|
||||||
|
|
||||||
class DelayProvider : IDelayProvider
|
internal class DelayProvider : IDelayProvider
|
||||||
{
|
{
|
||||||
public Task Delay(Int32 delay)
|
public Task Delay(Int32 delay)
|
||||||
{
|
{
|
||||||
|
|
@ -15,22 +15,25 @@ class DelayProvider : IDelayProvider
|
||||||
|
|
||||||
public class DownloadStationDownloader : IDownloader
|
public class DownloadStationDownloader : IDownloader
|
||||||
{
|
{
|
||||||
public event EventHandler<DownloadCompleteEventArgs>? DownloadComplete;
|
|
||||||
public event EventHandler<DownloadProgressEventArgs>? DownloadProgress;
|
|
||||||
|
|
||||||
private const Int32 RetryCount = 5;
|
private const Int32 RetryCount = 5;
|
||||||
|
private readonly IDelayProvider _delayProvider;
|
||||||
private readonly ISynologyClient _synologyClient;
|
private readonly String _filePath;
|
||||||
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly String _filePath;
|
|
||||||
private readonly String _uri;
|
|
||||||
private readonly String? _remotePath;
|
private readonly String? _remotePath;
|
||||||
private readonly IDelayProvider _delayProvider;
|
|
||||||
|
private readonly ISynologyClient _synologyClient;
|
||||||
|
private readonly String _uri;
|
||||||
|
|
||||||
private String? _gid;
|
private String? _gid;
|
||||||
|
|
||||||
public DownloadStationDownloader(String? gid, String uri, String? remotePath, String filePath, String downloadPath, ISynologyClient synologyClient, IDelayProvider? delayProvider = null)
|
public DownloadStationDownloader(String? gid,
|
||||||
|
String uri,
|
||||||
|
String? remotePath,
|
||||||
|
String filePath,
|
||||||
|
String downloadPath,
|
||||||
|
ISynologyClient synologyClient,
|
||||||
|
IDelayProvider? delayProvider = null)
|
||||||
{
|
{
|
||||||
_logger = Log.ForContext<DownloadStationDownloader>();
|
_logger = Log.ForContext<DownloadStationDownloader>();
|
||||||
_logger.Debug($"Instantiated new DownloadStation Downloader for URI {uri} to filePath {filePath} and downloadPath {downloadPath} and GID {gid}");
|
_logger.Debug($"Instantiated new DownloadStation Downloader for URI {uri} to filePath {filePath} and downloadPath {downloadPath} and GID {gid}");
|
||||||
|
|
@ -43,48 +46,8 @@ public class DownloadStationDownloader : IDownloader
|
||||||
_delayProvider = delayProvider ?? new DelayProvider();
|
_delayProvider = delayProvider ?? new DelayProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<DownloadStationDownloader> Init(String? gid, String uri, String filePath, String downloadPath, String? category)
|
public event EventHandler<DownloadCompleteEventArgs>? DownloadComplete;
|
||||||
{
|
public event EventHandler<DownloadProgressEventArgs>? DownloadProgress;
|
||||||
if (Settings.Get.DownloadClient.DownloadStationUrl == null)
|
|
||||||
{
|
|
||||||
throw new("No URL specified for Synology download station");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Settings.Get.DownloadClient.DownloadStationUsername == null || Settings.Get.DownloadClient.DownloadStationPassword == null)
|
|
||||||
{
|
|
||||||
throw new("No username/password specified for Synology download station");
|
|
||||||
}
|
|
||||||
|
|
||||||
var synologyClient = new SynologyClient(Settings.Get.DownloadClient.DownloadStationUrl);
|
|
||||||
await synologyClient.LoginAsync(Settings.Get.DownloadClient.DownloadStationUsername, Settings.Get.DownloadClient.DownloadStationPassword);
|
|
||||||
|
|
||||||
String? remotePath = null;
|
|
||||||
String? rootPath;
|
|
||||||
|
|
||||||
if (!String.IsNullOrWhiteSpace(Settings.Get.DownloadClient.DownloadStationDownloadPath))
|
|
||||||
{
|
|
||||||
rootPath = Settings.Get.DownloadClient.DownloadStationDownloadPath;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var config = await synologyClient.DownloadStationApi().InfoEndpoint().GetConfigAsync();
|
|
||||||
rootPath = config.DefaultDestination;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rootPath != null)
|
|
||||||
{
|
|
||||||
if (String.IsNullOrWhiteSpace(category))
|
|
||||||
{
|
|
||||||
remotePath = Path.Combine(rootPath, downloadPath).Replace('\\', '/');
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
remotePath = Path.Combine(rootPath, category, downloadPath).Replace('\\', '/');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new(gid, uri, remotePath, filePath, downloadPath, synologyClient);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Cancel()
|
public async Task Cancel()
|
||||||
{
|
{
|
||||||
|
|
@ -173,13 +136,6 @@ public class DownloadStationDownloader : IDownloader
|
||||||
throw new($"Unable to download file");
|
throw new($"Unable to download file");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<String?> GetGidFromUri()
|
|
||||||
{
|
|
||||||
var tasks = await _synologyClient.DownloadStationApi().TaskEndpoint().ListAsync();
|
|
||||||
|
|
||||||
return tasks.Task?.FirstOrDefault(t => t.Additional?.Detail?.Uri == _uri)?.Id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Pause()
|
public async Task Pause()
|
||||||
{
|
{
|
||||||
_logger.Debug($"Pausing download {_uri} {_gid}");
|
_logger.Debug($"Pausing download {_uri} {_gid}");
|
||||||
|
|
@ -200,6 +156,56 @@ public class DownloadStationDownloader : IDownloader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task<DownloadStationDownloader> Init(String? gid, String uri, String filePath, String downloadPath, String? category)
|
||||||
|
{
|
||||||
|
if (Settings.Get.DownloadClient.DownloadStationUrl == null)
|
||||||
|
{
|
||||||
|
throw new("No URL specified for Synology download station");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Settings.Get.DownloadClient.DownloadStationUsername == null || Settings.Get.DownloadClient.DownloadStationPassword == null)
|
||||||
|
{
|
||||||
|
throw new("No username/password specified for Synology download station");
|
||||||
|
}
|
||||||
|
|
||||||
|
var synologyClient = new SynologyClient(Settings.Get.DownloadClient.DownloadStationUrl);
|
||||||
|
await synologyClient.LoginAsync(Settings.Get.DownloadClient.DownloadStationUsername, Settings.Get.DownloadClient.DownloadStationPassword);
|
||||||
|
|
||||||
|
String? remotePath = null;
|
||||||
|
String? rootPath;
|
||||||
|
|
||||||
|
if (!String.IsNullOrWhiteSpace(Settings.Get.DownloadClient.DownloadStationDownloadPath))
|
||||||
|
{
|
||||||
|
rootPath = Settings.Get.DownloadClient.DownloadStationDownloadPath;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var config = await synologyClient.DownloadStationApi().InfoEndpoint().GetConfigAsync();
|
||||||
|
rootPath = config.DefaultDestination;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rootPath != null)
|
||||||
|
{
|
||||||
|
if (String.IsNullOrWhiteSpace(category))
|
||||||
|
{
|
||||||
|
remotePath = Path.Combine(rootPath, downloadPath).Replace('\\', '/');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
remotePath = Path.Combine(rootPath, category, downloadPath).Replace('\\', '/');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new(gid, uri, remotePath, filePath, downloadPath, synologyClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<String?> GetGidFromUri()
|
||||||
|
{
|
||||||
|
var tasks = await _synologyClient.DownloadStationApi().TaskEndpoint().ListAsync();
|
||||||
|
|
||||||
|
return tasks.Task?.FirstOrDefault(t => t.Additional?.Detail?.Uri == _uri)?.Id;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task Update()
|
public async Task Update()
|
||||||
{
|
{
|
||||||
if (_gid == null)
|
if (_gid == null)
|
||||||
|
|
|
||||||
|
|
@ -20,4 +20,4 @@ public interface IDownloader
|
||||||
Task Cancel();
|
Task Cancel();
|
||||||
Task Pause();
|
Task Pause();
|
||||||
Task Resume();
|
Task Resume();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,13 @@ namespace RdtClient.Service.Services.Downloaders;
|
||||||
|
|
||||||
public class SymlinkDownloader(String uri, String destinationPath, String path, Provider? clientKind) : IDownloader
|
public class SymlinkDownloader(String uri, String destinationPath, String path, Provider? clientKind) : IDownloader
|
||||||
{
|
{
|
||||||
public event EventHandler<DownloadCompleteEventArgs>? DownloadComplete;
|
private const Int32 MaxRetries = 10;
|
||||||
public event EventHandler<DownloadProgressEventArgs>? DownloadProgress;
|
|
||||||
|
|
||||||
private readonly CancellationTokenSource _cancellationToken = new();
|
private readonly CancellationTokenSource _cancellationToken = new();
|
||||||
|
|
||||||
private readonly ILogger _logger = Log.ForContext<SymlinkDownloader>();
|
private readonly ILogger _logger = Log.ForContext<SymlinkDownloader>();
|
||||||
|
public event EventHandler<DownloadCompleteEventArgs>? DownloadComplete;
|
||||||
private const Int32 MaxRetries = 10;
|
public event EventHandler<DownloadProgressEventArgs>? DownloadProgress;
|
||||||
|
|
||||||
public async Task<String> Download()
|
public async Task<String> Download()
|
||||||
{
|
{
|
||||||
|
|
@ -23,9 +22,9 @@ public class SymlinkDownloader(String uri, String destinationPath, String path,
|
||||||
{
|
{
|
||||||
var filePath = new FileInfo(path);
|
var filePath = new FileInfo(path);
|
||||||
|
|
||||||
var rcloneMountPath = Settings.Get.DownloadClient.RcloneMountPath.TrimEnd(['\\', '/']);
|
var rcloneMountPath = Settings.Get.DownloadClient.RcloneMountPath.TrimEnd('\\', '/');
|
||||||
var searchSubDirectories = rcloneMountPath.EndsWith('*');
|
var searchSubDirectories = rcloneMountPath.EndsWith('*');
|
||||||
rcloneMountPath = rcloneMountPath.TrimEnd('*').TrimEnd(['\\', '/']);
|
rcloneMountPath = rcloneMountPath.TrimEnd('*').TrimEnd('\\', '/');
|
||||||
|
|
||||||
if (!Directory.Exists(rcloneMountPath))
|
if (!Directory.Exists(rcloneMountPath))
|
||||||
{
|
{
|
||||||
|
|
@ -35,7 +34,7 @@ public class SymlinkDownloader(String uri, String destinationPath, String path,
|
||||||
var fileName = filePath.Name;
|
var fileName = filePath.Name;
|
||||||
var fileExtension = filePath.Extension;
|
var fileExtension = filePath.Extension;
|
||||||
var fileNameWithoutExtension = fileName.Replace(fileExtension, "");
|
var fileNameWithoutExtension = fileName.Replace(fileExtension, "");
|
||||||
var pathWithoutFileName = path.Replace(fileName, "").TrimEnd(['\\', '/']);
|
var pathWithoutFileName = path.Replace(fileName, "").TrimEnd('\\', '/');
|
||||||
var searchPath = Path.Combine(rcloneMountPath, pathWithoutFileName);
|
var searchPath = Path.Combine(rcloneMountPath, pathWithoutFileName);
|
||||||
|
|
||||||
List<String> unWantedExtensions =
|
List<String> unWantedExtensions =
|
||||||
|
|
@ -57,7 +56,7 @@ public class SymlinkDownloader(String uri, String destinationPath, String path,
|
||||||
BytesTotal = 0,
|
BytesTotal = 0,
|
||||||
Speed = 0
|
Speed = 0
|
||||||
});
|
});
|
||||||
|
|
||||||
String? file = null;
|
String? file = null;
|
||||||
var shouldSearch = true;
|
var shouldSearch = true;
|
||||||
|
|
||||||
|
|
@ -65,7 +64,7 @@ public class SymlinkDownloader(String uri, String destinationPath, String path,
|
||||||
if (clientKind == Provider.AllDebrid)
|
if (clientKind == Provider.AllDebrid)
|
||||||
{
|
{
|
||||||
var potentialFilePath = Path.Combine(rcloneMountPath, path);
|
var potentialFilePath = Path.Combine(rcloneMountPath, path);
|
||||||
|
|
||||||
// Make sure the file exists before making any assumptions.
|
// Make sure the file exists before making any assumptions.
|
||||||
// If this somehow fails, fallback to the search below.
|
// If this somehow fails, fallback to the search below.
|
||||||
if (File.Exists(potentialFilePath))
|
if (File.Exists(potentialFilePath))
|
||||||
|
|
@ -95,7 +94,7 @@ public class SymlinkDownloader(String uri, String destinationPath, String path,
|
||||||
potentialFilePaths.Add(directoryInfo.Name);
|
potentialFilePaths.Add(directoryInfo.Name);
|
||||||
directoryInfo = directoryInfo.Parent;
|
directoryInfo = directoryInfo.Parent;
|
||||||
|
|
||||||
if (directoryInfo.FullName.TrimEnd(['\\', '/']) == rcloneMountPath)
|
if (directoryInfo.FullName.TrimEnd('\\', '/') == rcloneMountPath)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -182,10 +181,11 @@ public class SymlinkDownloader(String uri, String destinationPath, String path,
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
DownloadComplete?.Invoke(this, new()
|
DownloadComplete?.Invoke(this,
|
||||||
{
|
new()
|
||||||
Error = ex.Message
|
{
|
||||||
});
|
Error = ex.Message
|
||||||
|
});
|
||||||
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ public class Downloads(DownloadData downloadData) : IDownloads
|
||||||
{
|
{
|
||||||
await downloadData.UpdateError(downloadId, error);
|
await downloadData.UpdateError(downloadId, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateRetryCount(Guid downloadId, Int32 retryCount)
|
public async Task UpdateRetryCount(Guid downloadId, Int32 retryCount)
|
||||||
{
|
{
|
||||||
await downloadData.UpdateRetryCount(downloadId, retryCount);
|
await downloadData.UpdateRetryCount(downloadId, retryCount);
|
||||||
|
|
@ -80,14 +80,14 @@ public class Downloads(DownloadData downloadData) : IDownloads
|
||||||
{
|
{
|
||||||
await downloadData.UpdateRemoteId(downloadId, remoteId);
|
await downloadData.UpdateRemoteId(downloadId, remoteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeleteForTorrent(Guid torrentId)
|
public async Task DeleteForTorrent(Guid torrentId)
|
||||||
{
|
{
|
||||||
await downloadData.DeleteForTorrent(torrentId);
|
await downloadData.DeleteForTorrent(torrentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Reset(Guid downloadId)
|
public async Task Reset(Guid downloadId)
|
||||||
{
|
{
|
||||||
await downloadData.Reset(downloadId);
|
await downloadData.Reset(downloadId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,12 @@ public interface IEnricher
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enriches magnet links and torrents by adding trackers from the tracker list grabber.
|
/// Enriches magnet links and torrents by adding trackers from the tracker list grabber.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class Enricher(ILogger<Enricher> logger, ITrackerListGrabber trackerListGrabber) : IEnricher
|
public sealed class Enricher(ILogger<Enricher> logger, ITrackerListGrabber trackerListGrabber) : IEnricher
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add trackers from the tracker list grabber to the magnet link.
|
/// Add trackers from the tracker list grabber to the magnet link.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="magnetLink">Magnet link to add trackers to. Is not modified</param>
|
/// <param name="magnetLink">Magnet link to add trackers to. Is not modified</param>
|
||||||
/// <returns>Magnet link with additional trackers</returns>
|
/// <returns>Magnet link with additional trackers</returns>
|
||||||
|
|
@ -127,7 +127,7 @@ public sealed class Enricher(ILogger<Enricher> logger, ITrackerListGrabber track
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add trackers from the tracker list grabber to the .torrent file bytes.
|
/// Add trackers from the tracker list grabber to the .torrent file bytes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="torrentBytes">Torrent file bytes to add trackers to. Is not modified</param>
|
/// <param name="torrentBytes">Torrent file bytes to add trackers to. Is not modified</param>
|
||||||
/// <returns>Torrent file bytes with additional trackers</returns>
|
/// <returns>Torrent file bytes with additional trackers</returns>
|
||||||
|
|
@ -223,4 +223,4 @@ public sealed class Enricher(ILogger<Enricher> logger, ITrackerListGrabber track
|
||||||
|
|
||||||
return torrentDict.Encode();
|
return torrentDict.Encode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,4 +3,4 @@ namespace RdtClient.Service.Services;
|
||||||
public interface ITrackerListGrabber
|
public interface ITrackerListGrabber
|
||||||
{
|
{
|
||||||
Task<String[]> GetTrackers();
|
Task<String[]> GetTrackers();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -206,13 +206,15 @@ public class QBittorrent(ILogger<QBittorrent> logger, Settings settings, Authent
|
||||||
}
|
}
|
||||||
|
|
||||||
var torrentPath = downloadPath;
|
var torrentPath = downloadPath;
|
||||||
|
|
||||||
if (!String.IsNullOrWhiteSpace(torrent.RdName))
|
if (!String.IsNullOrWhiteSpace(torrent.RdName))
|
||||||
{
|
{
|
||||||
// Alldebrid stores single file torrents at the root folder.
|
// Alldebrid stores single file torrents at the root folder.
|
||||||
if (torrent.ClientKind == Provider.AllDebrid && torrent.Files.Count == 1)
|
if (torrent.ClientKind == Provider.AllDebrid && torrent.Files.Count == 1)
|
||||||
{
|
{
|
||||||
torrentPath = Path.Combine(downloadPath, torrent.Files[0].Path);
|
torrentPath = Path.Combine(downloadPath, torrent.Files[0].Path);
|
||||||
} else
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
torrentPath = Path.Combine(downloadPath, torrent.RdName) + Path.DirectorySeparatorChar;
|
torrentPath = Path.Combine(downloadPath, torrent.RdName) + Path.DirectorySeparatorChar;
|
||||||
}
|
}
|
||||||
|
|
@ -222,29 +224,31 @@ public class QBittorrent(ILogger<QBittorrent> logger, Settings settings, Authent
|
||||||
Int64 bytesTotal = 0;
|
Int64 bytesTotal = 0;
|
||||||
Int64 speed = 0;
|
Int64 speed = 0;
|
||||||
Double rdProgress = 0;
|
Double rdProgress = 0;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
rdProgress = (torrent.RdProgress ?? 0.0) / 100.0;
|
rdProgress = (torrent.RdProgress ?? 0.0) / 100.0;
|
||||||
bytesTotal = torrent.RdSize ?? 1;
|
bytesTotal = torrent.RdSize ?? 1;
|
||||||
bytesDone = (Int64) (bytesTotal * rdProgress);
|
bytesDone = (Int64)(bytesTotal * rdProgress);
|
||||||
speed = torrent.RdSpeed ?? 0;
|
speed = torrent.RdSpeed ?? 0;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
logger.LogError(ex, $"""
|
logger.LogError(ex,
|
||||||
Error calculating progress:
|
$"""
|
||||||
RdProgress: {torrent.RdProgress}
|
Error calculating progress:
|
||||||
RdSize: {torrent.RdSize}
|
RdProgress: {torrent.RdProgress}
|
||||||
RdSpeed: {torrent.RdSpeed}
|
RdSize: {torrent.RdSize}
|
||||||
""");
|
RdSpeed: {torrent.RdSpeed}
|
||||||
|
""");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (torrent.Downloads.Count > 0)
|
if (torrent.Downloads.Count > 0)
|
||||||
{
|
{
|
||||||
bytesDone = torrent.Downloads.Sum(m => m.BytesDone);
|
bytesDone = torrent.Downloads.Sum(m => m.BytesDone);
|
||||||
bytesTotal = torrent.Downloads.Sum(m => m.BytesTotal);
|
bytesTotal = torrent.Downloads.Sum(m => m.BytesTotal);
|
||||||
speed = (Int32) torrent.Downloads.Average(m => m.Speed);
|
speed = (Int32)torrent.Downloads.Average(m => m.Speed);
|
||||||
downloadProgress = bytesTotal > 0 ? (Double) bytesDone / bytesTotal : 0;
|
downloadProgress = bytesTotal > 0 ? (Double)bytesDone / bytesTotal : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
var progress = (rdProgress + downloadProgress) / 2.0;
|
var progress = (rdProgress + downloadProgress) / 2.0;
|
||||||
|
|
@ -288,7 +292,7 @@ public class QBittorrent(ILogger<QBittorrent> logger, Settings settings, Authent
|
||||||
State = "downloading",
|
State = "downloading",
|
||||||
SuperSeeding = false,
|
SuperSeeding = false,
|
||||||
Tags = "",
|
Tags = "",
|
||||||
TimeActive = (Int64) (DateTimeOffset.UtcNow - torrent.Added).TotalSeconds,
|
TimeActive = (Int64)(DateTimeOffset.UtcNow - torrent.Added).TotalSeconds,
|
||||||
TotalSize = bytesTotal,
|
TotalSize = bytesTotal,
|
||||||
Tracker = "udp://tracker.opentrackr.org:1337",
|
Tracker = "udp://tracker.opentrackr.org:1337",
|
||||||
UpLimit = -1,
|
UpLimit = -1,
|
||||||
|
|
@ -364,7 +368,7 @@ public class QBittorrent(ILogger<QBittorrent> logger, Settings settings, Authent
|
||||||
{
|
{
|
||||||
bytesDone = torrent.Downloads.Sum(m => m.BytesDone);
|
bytesDone = torrent.Downloads.Sum(m => m.BytesDone);
|
||||||
bytesTotal = torrent.Downloads.Sum(m => m.BytesTotal);
|
bytesTotal = torrent.Downloads.Sum(m => m.BytesTotal);
|
||||||
speed = (Int32) torrent.Downloads.Average(m => m.Speed);
|
speed = (Int32)torrent.Downloads.Average(m => m.Speed);
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = new TorrentProperties
|
var result = new TorrentProperties
|
||||||
|
|
@ -392,7 +396,7 @@ public class QBittorrent(ILogger<QBittorrent> logger, Settings settings, Authent
|
||||||
Seeds = 100,
|
Seeds = 100,
|
||||||
SeedsTotal = 100,
|
SeedsTotal = 100,
|
||||||
ShareRatio = 9999,
|
ShareRatio = 9999,
|
||||||
TimeElapsed = (Int64) (DateTimeOffset.UtcNow - torrent.Added).TotalSeconds,
|
TimeElapsed = (Int64)(DateTimeOffset.UtcNow - torrent.Added).TotalSeconds,
|
||||||
TotalDownloaded = bytesDone,
|
TotalDownloaded = bytesDone,
|
||||||
TotalDownloadedSession = bytesDone,
|
TotalDownloadedSession = bytesDone,
|
||||||
TotalSize = bytesTotal,
|
TotalSize = bytesTotal,
|
||||||
|
|
@ -513,8 +517,8 @@ public class QBittorrent(ILogger<QBittorrent> logger, Settings settings, Authent
|
||||||
var allTorrents = await torrents.Get();
|
var allTorrents = await torrents.Get();
|
||||||
|
|
||||||
var torrentsToGroup = allTorrents.Where(m => !String.IsNullOrWhiteSpace(m.Category))
|
var torrentsToGroup = allTorrents.Where(m => !String.IsNullOrWhiteSpace(m.Category))
|
||||||
.Select(m => m.Category!.ToLower())
|
.Select(m => m.Category!.ToLower())
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var categoryList = (Settings.Get.General.Categories ?? "")
|
var categoryList = (Settings.Get.General.Categories ?? "")
|
||||||
.Split(",", StringSplitOptions.RemoveEmptyEntries)
|
.Split(",", StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
|
@ -672,4 +676,4 @@ public class QBittorrent(ILogger<QBittorrent> logger, Settings settings, Authent
|
||||||
DlRateLimit = Settings.Get.DownloadClient.MaxSpeed
|
DlRateLimit = Settings.Get.DownloadClient.MaxSpeed
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,4 +20,4 @@ public class RdtHub : Hub
|
||||||
Users.TryRemove(Context.ConnectionId, out _);
|
Users.TryRemove(Context.ConnectionId, out _);
|
||||||
await base.OnDisconnectedAsync(exception);
|
await base.OnDisconnectedAsync(exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,13 @@ public class RemoteService(IHubContext<RdtHub> hub, Torrents torrents)
|
||||||
public async Task Update()
|
public async Task Update()
|
||||||
{
|
{
|
||||||
var allTorrents = await torrents.Get();
|
var allTorrents = await torrents.Get();
|
||||||
|
|
||||||
// Prevent infinite recursion when serializing
|
// Prevent infinite recursion when serializing
|
||||||
foreach (var file in allTorrents.SelectMany(torrent => torrent.Downloads))
|
foreach (var file in allTorrents.SelectMany(torrent => torrent.Downloads))
|
||||||
{
|
{
|
||||||
file.Torrent = null;
|
file.Torrent = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
await hub.Clients.All.SendCoreAsync("update",
|
await hub.Clients.All.SendCoreAsync("update",
|
||||||
[
|
[
|
||||||
allTorrents
|
allTorrents
|
||||||
|
|
@ -24,4 +24,4 @@ public class RemoteService(IHubContext<RdtHub> hub, Torrents torrents)
|
||||||
{
|
{
|
||||||
await hub.Clients.All.SendCoreAsync("diskSpaceStatus", [status]);
|
await hub.Clients.All.SendCoreAsync("diskSpaceStatus", [status]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ public class Settings(SettingData settingData)
|
||||||
{
|
{
|
||||||
await settingData.ResetCache();
|
await settingData.ResetCache();
|
||||||
|
|
||||||
LoggingLevelSwitch.MinimumLevel = Settings.Get.General.LogLevel switch
|
LoggingLevelSwitch.MinimumLevel = Get.General.LogLevel switch
|
||||||
{
|
{
|
||||||
LogLevel.Verbose => LogEventLevel.Verbose,
|
LogLevel.Verbose => LogEventLevel.Verbose,
|
||||||
LogLevel.Debug => LogEventLevel.Debug,
|
LogLevel.Debug => LogEventLevel.Debug,
|
||||||
|
|
@ -56,4 +56,4 @@ public class Settings(SettingData settingData)
|
||||||
_ => LogEventLevel.Warning
|
_ => LogEventLevel.Warning
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,9 @@ using AllDebridNET;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using RdtClient.Data.Enums;
|
using RdtClient.Data.Enums;
|
||||||
|
using RdtClient.Data.Models.Data;
|
||||||
using RdtClient.Data.Models.TorrentClient;
|
using RdtClient.Data.Models.TorrentClient;
|
||||||
using RdtClient.Service.Helpers;
|
using RdtClient.Service.Helpers;
|
||||||
using RdtClient.Data.Models.Data;
|
|
||||||
using File = AllDebridNET.File;
|
using File = AllDebridNET.File;
|
||||||
using Torrent = RdtClient.Data.Models.Data.Torrent;
|
using Torrent = RdtClient.Data.Models.Data.Torrent;
|
||||||
|
|
||||||
|
|
@ -51,36 +51,12 @@ public class AllDebridNetClientFactory(ILogger<AllDebridNetClientFactory> logger
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AllDebridTorrentClient(ILogger<AllDebridTorrentClient> logger, IAllDebridNetClientFactory allDebridNetClientFactory, IDownloadableFileFilter fileFilter) : ITorrentClient
|
public class AllDebridTorrentClient(ILogger<AllDebridTorrentClient> logger, IAllDebridNetClientFactory allDebridNetClientFactory, IDownloadableFileFilter fileFilter)
|
||||||
|
: ITorrentClient
|
||||||
{
|
{
|
||||||
private static readonly Int64 SessionId = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
private static readonly Int64 SessionId = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||||
private static List<TorrentClientTorrent> _cache = [];
|
private static List<TorrentClientTorrent> _cache = [];
|
||||||
private static Int64 _sessionCounter = 0;
|
private static Int64 _sessionCounter;
|
||||||
|
|
||||||
private static TorrentClientTorrent Map(Magnet torrent)
|
|
||||||
{
|
|
||||||
var files = GetFiles(torrent.Files);
|
|
||||||
return new()
|
|
||||||
{
|
|
||||||
Id = torrent.Id.ToString(),
|
|
||||||
Filename = torrent.Filename ?? "",
|
|
||||||
OriginalFilename = torrent.Filename,
|
|
||||||
Hash = torrent.Hash ?? "",
|
|
||||||
Bytes = torrent.Size ?? 0,
|
|
||||||
OriginalBytes = torrent.Size ?? 0,
|
|
||||||
Host = null,
|
|
||||||
Split = 0,
|
|
||||||
Progress = (Int64)Math.Round((torrent.Downloaded ?? 0) * 100.0 / (torrent.Size ?? 1)),
|
|
||||||
Status = torrent.Status,
|
|
||||||
StatusCode = torrent.StatusCode ?? 0,
|
|
||||||
Added = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(torrent.UploadDate ?? 0),
|
|
||||||
Files = files,
|
|
||||||
Links = [],
|
|
||||||
Ended = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(torrent.CompletionDate ?? 0),
|
|
||||||
Speed = torrent.DownloadSpeed,
|
|
||||||
Seeders = torrent.Seeders
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IList<TorrentClientTorrent>> GetTorrents()
|
public async Task<IList<TorrentClientTorrent>> GetTorrents()
|
||||||
{
|
{
|
||||||
|
|
@ -98,10 +74,12 @@ public class AllDebridTorrentClient(ILogger<AllDebridTorrentClient> logger, IAll
|
||||||
foreach (var result in results.Magnets ?? [])
|
foreach (var result in results.Magnets ?? [])
|
||||||
{
|
{
|
||||||
var existing = _cache.FirstOrDefault(m => m.Id == result.Id.ToString());
|
var existing = _cache.FirstOrDefault(m => m.Id == result.Id.ToString());
|
||||||
|
|
||||||
if (existing != null)
|
if (existing != null)
|
||||||
{
|
{
|
||||||
_cache.Remove(existing);
|
_cache.Remove(existing);
|
||||||
}
|
}
|
||||||
|
|
||||||
_cache.Add(Map(result));
|
_cache.Add(Map(result));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -252,18 +230,20 @@ public class AllDebridTorrentClient(ILogger<AllDebridTorrentClient> logger, IAll
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log($"Getting download links", torrent);
|
Log($"Getting download links", torrent);
|
||||||
|
|
||||||
var allFiles = await allDebridNetClientFactory.GetClient().Magnet.FilesAsync(Int64.Parse(torrent.RdId));
|
var allFiles = await allDebridNetClientFactory.GetClient().Magnet.FilesAsync(Int64.Parse(torrent.RdId));
|
||||||
|
|
||||||
var files = GetFiles(allFiles);
|
var files = GetFiles(allFiles);
|
||||||
|
|
||||||
return files.Where(f => fileFilter.IsDownloadable(torrent, f.Path, f.Bytes) && f.DownloadLink != null).Select(f => new DownloadInfo
|
return files.Where(f => fileFilter.IsDownloadable(torrent, f.Path, f.Bytes) && f.DownloadLink != null)
|
||||||
{
|
.Select(f => new DownloadInfo
|
||||||
FileName = Path.GetFileName(f.Path),
|
{
|
||||||
RestrictedLink = f.DownloadLink!
|
FileName = Path.GetFileName(f.Path),
|
||||||
}).ToList();
|
RestrictedLink = f.DownloadLink!
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
@ -271,17 +251,43 @@ public class AllDebridTorrentClient(ILogger<AllDebridTorrentClient> logger, IAll
|
||||||
{
|
{
|
||||||
// FileName is set in GetDownlaadInfos
|
// FileName is set in GetDownlaadInfos
|
||||||
Debug.Assert(download.FileName != null);
|
Debug.Assert(download.FileName != null);
|
||||||
|
|
||||||
return Task.FromResult(download.FileName);
|
return Task.FromResult(download.FileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static TorrentClientTorrent Map(Magnet torrent)
|
||||||
|
{
|
||||||
|
var files = GetFiles(torrent.Files);
|
||||||
|
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Id = torrent.Id.ToString(),
|
||||||
|
Filename = torrent.Filename ?? "",
|
||||||
|
OriginalFilename = torrent.Filename,
|
||||||
|
Hash = torrent.Hash ?? "",
|
||||||
|
Bytes = torrent.Size ?? 0,
|
||||||
|
OriginalBytes = torrent.Size ?? 0,
|
||||||
|
Host = null,
|
||||||
|
Split = 0,
|
||||||
|
Progress = (Int64)Math.Round(((torrent.Downloaded ?? 0) * 100.0) / (torrent.Size ?? 1)),
|
||||||
|
Status = torrent.Status,
|
||||||
|
StatusCode = torrent.StatusCode ?? 0,
|
||||||
|
Added = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(torrent.UploadDate ?? 0),
|
||||||
|
Files = files,
|
||||||
|
Links = [],
|
||||||
|
Ended = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(torrent.CompletionDate ?? 0),
|
||||||
|
Speed = torrent.DownloadSpeed,
|
||||||
|
Seeders = torrent.Seeders
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<TorrentClientTorrent> GetInfo(String torrentId)
|
private async Task<TorrentClientTorrent> GetInfo(String torrentId)
|
||||||
{
|
{
|
||||||
var result = await allDebridNetClientFactory.GetClient().Magnet.StatusAsync(torrentId) ?? throw new($"Unable to find magnet with ID {torrentId}");
|
var result = await allDebridNetClientFactory.GetClient().Magnet.StatusAsync(torrentId) ?? throw new($"Unable to find magnet with ID {torrentId}");
|
||||||
|
|
||||||
return Map(result);
|
return Map(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<TorrentClientFile> GetFiles(List<File>? files, String parentPath = "")
|
private static List<TorrentClientFile> GetFiles(List<File>? files, String parentPath = "")
|
||||||
{
|
{
|
||||||
if (files == null)
|
if (files == null)
|
||||||
|
|
@ -290,32 +296,33 @@ public class AllDebridTorrentClient(ILogger<AllDebridTorrentClient> logger, IAll
|
||||||
}
|
}
|
||||||
|
|
||||||
return files.SelectMany(file =>
|
return files.SelectMany(file =>
|
||||||
{
|
{
|
||||||
var currentPath = String.IsNullOrEmpty(parentPath)
|
var currentPath = String.IsNullOrEmpty(parentPath)
|
||||||
? file.FolderOrFileName
|
? file.FolderOrFileName
|
||||||
: Path.Combine(parentPath, file.FolderOrFileName);
|
: Path.Combine(parentPath, file.FolderOrFileName);
|
||||||
|
|
||||||
var result = new List<TorrentClientFile>();
|
var result = new List<TorrentClientFile>();
|
||||||
|
|
||||||
// If it's a file (has size)
|
// If it's a file (has size)
|
||||||
if (file.Size.HasValue)
|
if (file.Size.HasValue)
|
||||||
{
|
{
|
||||||
result.Add(new()
|
result.Add(new()
|
||||||
{
|
{
|
||||||
Path = currentPath,
|
Path = currentPath,
|
||||||
Bytes = file.Size.Value,
|
Bytes = file.Size.Value,
|
||||||
DownloadLink = file.DownloadLink
|
DownloadLink = file.DownloadLink
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process sub-nodes if they exist
|
// Process sub-nodes if they exist
|
||||||
if (file.SubNodes != null)
|
if (file.SubNodes != null)
|
||||||
{
|
{
|
||||||
result.AddRange(GetFiles(file.SubNodes, currentPath));
|
result.AddRange(GetFiles(file.SubNodes, currentPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}).ToList();
|
})
|
||||||
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Log(String message, Torrent? torrent = null)
|
private void Log(String message, Torrent? torrent = null)
|
||||||
|
|
@ -336,7 +343,7 @@ public class AllDebridTorrentClient(ILogger<AllDebridTorrentClient> logger, IAll
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var directory = DownloadHelper.RemoveInvalidPathChars(torrent.RdName);
|
var directory = DownloadHelper.RemoveInvalidPathChars(torrent.RdName);
|
||||||
|
|
||||||
var matchingTorrentFiles = torrent.Files.Where(m => m.Path.EndsWith(fileName)).Where(m => !String.IsNullOrWhiteSpace(m.Path)).ToList();
|
var matchingTorrentFiles = torrent.Files.Where(m => m.Path.EndsWith(fileName)).Where(m => !String.IsNullOrWhiteSpace(m.Path)).ToList();
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using DebridLinkFrNET;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using DebridLinkFrNET;
|
|
||||||
using RdtClient.Data.Enums;
|
using RdtClient.Data.Enums;
|
||||||
|
using RdtClient.Data.Models.Data;
|
||||||
using RdtClient.Data.Models.TorrentClient;
|
using RdtClient.Data.Models.TorrentClient;
|
||||||
using RdtClient.Service.Helpers;
|
using RdtClient.Service.Helpers;
|
||||||
using RdtClient.Data.Models.Data;
|
|
||||||
using Download = RdtClient.Data.Models.Data.Download;
|
using Download = RdtClient.Data.Models.Data.Download;
|
||||||
using Torrent = DebridLinkFrNET.Models.Torrent;
|
using Torrent = DebridLinkFrNET.Models.Torrent;
|
||||||
|
|
||||||
|
|
@ -13,78 +13,6 @@ namespace RdtClient.Service.Services.TorrentClients;
|
||||||
|
|
||||||
public class DebridLinkClient(ILogger<DebridLinkClient> logger, IHttpClientFactory httpClientFactory, IDownloadableFileFilter fileFilter) : ITorrentClient
|
public class DebridLinkClient(ILogger<DebridLinkClient> logger, IHttpClientFactory httpClientFactory, IDownloadableFileFilter fileFilter) : ITorrentClient
|
||||||
{
|
{
|
||||||
private DebridLinkFrNETClient GetClient()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var apiKey = Settings.Get.Provider.ApiKey;
|
|
||||||
|
|
||||||
if (String.IsNullOrWhiteSpace(apiKey))
|
|
||||||
{
|
|
||||||
throw new("DebridLink API Key not set in the settings");
|
|
||||||
}
|
|
||||||
|
|
||||||
var httpClient = httpClientFactory.CreateClient();
|
|
||||||
httpClient.Timeout = TimeSpan.FromSeconds(Settings.Get.Provider.Timeout);
|
|
||||||
|
|
||||||
var debridLinkClient = new DebridLinkFrNETClient(apiKey, httpClient);
|
|
||||||
|
|
||||||
return debridLinkClient;
|
|
||||||
}
|
|
||||||
catch (AggregateException ae)
|
|
||||||
{
|
|
||||||
foreach (var inner in ae.InnerExceptions)
|
|
||||||
{
|
|
||||||
logger.LogError(inner, $"The connection to DebridLink has failed: {inner.Message}");
|
|
||||||
}
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
|
|
||||||
{
|
|
||||||
logger.LogError(ex, $"The connection to DebridLink has timed out: {ex.Message}");
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch (TaskCanceledException ex)
|
|
||||||
{
|
|
||||||
logger.LogError(ex, $"The connection to DebridLink has timed out: {ex.Message}");
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private TorrentClientTorrent Map(Torrent torrent)
|
|
||||||
{
|
|
||||||
return new()
|
|
||||||
{
|
|
||||||
Id = torrent.Id ?? "",
|
|
||||||
Filename = torrent.Name ?? "",
|
|
||||||
OriginalFilename = torrent.Name ?? "",
|
|
||||||
Hash = torrent.HashString ?? "",
|
|
||||||
Bytes = torrent.TotalSize,
|
|
||||||
OriginalBytes = 0,
|
|
||||||
Host = torrent.ServerId ?? "",
|
|
||||||
Split = 0,
|
|
||||||
Progress = torrent.DownloadPercent,
|
|
||||||
Status = torrent.Status.ToString(),
|
|
||||||
Added = DateTimeOffset.FromUnixTimeSeconds(torrent.Created),
|
|
||||||
Files = (torrent.Files ?? []).Select((m, i) => new TorrentClientFile
|
|
||||||
{
|
|
||||||
Path = m.Name ?? "",
|
|
||||||
Bytes = m.Size,
|
|
||||||
Id = i,
|
|
||||||
Selected = true,
|
|
||||||
DownloadLink = m.DownloadUrl
|
|
||||||
})
|
|
||||||
.ToList(),
|
|
||||||
Links = torrent.Files?.Select(m => m.DownloadUrl.ToString()).ToList(),
|
|
||||||
Ended = null,
|
|
||||||
Speed = torrent.UploadSpeed,
|
|
||||||
Seeders = torrent.PeersConnected,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IList<TorrentClientTorrent>> GetTorrents()
|
public async Task<IList<TorrentClientTorrent>> GetTorrents()
|
||||||
{
|
{
|
||||||
var page = 0;
|
var page = 0;
|
||||||
|
|
@ -92,7 +20,7 @@ public class DebridLinkClient(ILogger<DebridLinkClient> logger, IHttpClientFacto
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
var pagedResults = await GetClient().Seedbox.ListAsync(null,page, 50);
|
var pagedResults = await GetClient().Seedbox.ListAsync(null, page, 50);
|
||||||
|
|
||||||
results.AddRange(pagedResults);
|
results.AddRange(pagedResults);
|
||||||
|
|
||||||
|
|
@ -110,7 +38,7 @@ public class DebridLinkClient(ILogger<DebridLinkClient> logger, IHttpClientFacto
|
||||||
public async Task<TorrentClientUser> GetUser()
|
public async Task<TorrentClientUser> GetUser()
|
||||||
{
|
{
|
||||||
var user = await GetClient().Account.Infos();
|
var user = await GetClient().Account.Infos();
|
||||||
|
|
||||||
return new()
|
return new()
|
||||||
{
|
{
|
||||||
Username = user.Username,
|
Username = user.Username,
|
||||||
|
|
@ -198,16 +126,16 @@ public class DebridLinkClient(ILogger<DebridLinkClient> logger, IHttpClientFacto
|
||||||
torrent.RdSeeders = rdTorrent.Seeders;
|
torrent.RdSeeders = rdTorrent.Seeders;
|
||||||
torrent.RdStatusRaw = rdTorrent.Status;
|
torrent.RdStatusRaw = rdTorrent.Status;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
0 Torrent is stopped
|
0 Torrent is stopped
|
||||||
1 Torrent is queued to verify local data
|
1 Torrent is queued to verify local data
|
||||||
2 Torrent is verifying local data
|
2 Torrent is verifying local data
|
||||||
3 Torrent is queued to download
|
3 Torrent is queued to download
|
||||||
4 Torrent is downloading
|
4 Torrent is downloading
|
||||||
5 Torrent is queued to seed
|
5 Torrent is queued to seed
|
||||||
6 Torrent is seeding
|
6 Torrent is seeding
|
||||||
100 Torrent is stored
|
100 Torrent is stored
|
||||||
*/
|
*/
|
||||||
|
|
||||||
torrent.RdStatus = rdTorrent.Status switch
|
torrent.RdStatus = rdTorrent.Status switch
|
||||||
{
|
{
|
||||||
|
|
@ -260,6 +188,87 @@ public class DebridLinkClient(ILogger<DebridLinkClient> logger, IHttpClientFacto
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task<String> GetFileName(Download download)
|
||||||
|
{
|
||||||
|
// FileName is set in GetDownlaadInfos
|
||||||
|
Debug.Assert(download.FileName != null);
|
||||||
|
|
||||||
|
return Task.FromResult(download.FileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DebridLinkFrNETClient GetClient()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var apiKey = Settings.Get.Provider.ApiKey;
|
||||||
|
|
||||||
|
if (String.IsNullOrWhiteSpace(apiKey))
|
||||||
|
{
|
||||||
|
throw new("DebridLink API Key not set in the settings");
|
||||||
|
}
|
||||||
|
|
||||||
|
var httpClient = httpClientFactory.CreateClient();
|
||||||
|
httpClient.Timeout = TimeSpan.FromSeconds(Settings.Get.Provider.Timeout);
|
||||||
|
|
||||||
|
var debridLinkClient = new DebridLinkFrNETClient(apiKey, httpClient);
|
||||||
|
|
||||||
|
return debridLinkClient;
|
||||||
|
}
|
||||||
|
catch (AggregateException ae)
|
||||||
|
{
|
||||||
|
foreach (var inner in ae.InnerExceptions)
|
||||||
|
{
|
||||||
|
logger.LogError(inner, $"The connection to DebridLink has failed: {inner.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, $"The connection to DebridLink has timed out: {ex.Message}");
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException ex)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, $"The connection to DebridLink has timed out: {ex.Message}");
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TorrentClientTorrent Map(Torrent torrent)
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Id = torrent.Id ?? "",
|
||||||
|
Filename = torrent.Name ?? "",
|
||||||
|
OriginalFilename = torrent.Name ?? "",
|
||||||
|
Hash = torrent.HashString ?? "",
|
||||||
|
Bytes = torrent.TotalSize,
|
||||||
|
OriginalBytes = 0,
|
||||||
|
Host = torrent.ServerId ?? "",
|
||||||
|
Split = 0,
|
||||||
|
Progress = torrent.DownloadPercent,
|
||||||
|
Status = torrent.Status.ToString(),
|
||||||
|
Added = DateTimeOffset.FromUnixTimeSeconds(torrent.Created),
|
||||||
|
Files = (torrent.Files ?? []).Select((m, i) => new TorrentClientFile
|
||||||
|
{
|
||||||
|
Path = m.Name ?? "",
|
||||||
|
Bytes = m.Size,
|
||||||
|
Id = i,
|
||||||
|
Selected = true,
|
||||||
|
DownloadLink = m.DownloadUrl
|
||||||
|
})
|
||||||
|
.ToList(),
|
||||||
|
Links = torrent.Files?.Select(m => m.DownloadUrl.ToString()).ToList(),
|
||||||
|
Ended = null,
|
||||||
|
Speed = torrent.UploadSpeed,
|
||||||
|
Seeders = torrent.PeersConnected
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<TorrentClientTorrent> GetInfo(String torrentId)
|
private async Task<TorrentClientTorrent> GetInfo(String torrentId)
|
||||||
{
|
{
|
||||||
var result = await GetClient().Seedbox.ListAsync(torrentId);
|
var result = await GetClient().Seedbox.ListAsync(torrentId);
|
||||||
|
|
@ -277,15 +286,6 @@ public class DebridLinkClient(ILogger<DebridLinkClient> logger, IHttpClientFacto
|
||||||
logger.LogDebug(message);
|
logger.LogDebug(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public Task<String> GetFileName(Download download)
|
|
||||||
{
|
|
||||||
// FileName is set in GetDownlaadInfos
|
|
||||||
Debug.Assert(download.FileName != null);
|
|
||||||
|
|
||||||
return Task.FromResult(download.FileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String? GetSymlinkPath(Data.Models.Data.Torrent torrent, Download download)
|
public static String? GetSymlinkPath(Data.Models.Data.Torrent torrent, Download download)
|
||||||
{
|
{
|
||||||
if (torrent.RdName == null || download.FileName == null)
|
if (torrent.RdName == null || download.FileName == null)
|
||||||
|
|
@ -301,4 +301,4 @@ public class DebridLinkClient(ILogger<DebridLinkClient> logger, IHttpClientFacto
|
||||||
|
|
||||||
return Path.Combine(torrent.RdName, download.FileName);
|
return Path.Combine(torrent.RdName, download.FileName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,24 +10,28 @@ public interface ITorrentClient
|
||||||
Task<String> AddMagnet(String magnetLink);
|
Task<String> AddMagnet(String magnetLink);
|
||||||
Task<String> AddFile(Byte[] bytes);
|
Task<String> AddFile(Byte[] bytes);
|
||||||
Task<IList<TorrentClientAvailableFile>> GetAvailableFiles(String hash);
|
Task<IList<TorrentClientAvailableFile>> GetAvailableFiles(String hash);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tell the debrid provider which files to download.
|
/// Tell the debrid provider which files to download.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remark>
|
/// <remark>
|
||||||
/// Not all providers support this feature.
|
/// Not all providers support this feature.
|
||||||
/// </remark>
|
/// </remark>
|
||||||
/// <param name="torrent">The torrent to select files for</param>
|
/// <param name="torrent">The torrent to select files for</param>
|
||||||
/// <returns>Number of files selected</returns>
|
/// <returns>Number of files selected</returns>
|
||||||
Task<Int32?> SelectFiles(Torrent torrent);
|
Task<Int32?> SelectFiles(Torrent torrent);
|
||||||
|
|
||||||
Task Delete(String torrentId);
|
Task Delete(String torrentId);
|
||||||
Task<String> Unrestrict(String link);
|
Task<String> Unrestrict(String link);
|
||||||
Task<Torrent> UpdateData(Torrent torrent, TorrentClientTorrent? torrentClientTorrent);
|
Task<Torrent> UpdateData(Torrent torrent, TorrentClientTorrent? torrentClientTorrent);
|
||||||
Task<IList<DownloadInfo>?> GetDownloadInfos(Torrent torrent);
|
Task<IList<DownloadInfo>?> GetDownloadInfos(Torrent torrent);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// To be called only when <see cref="Data.Models.Data.Download" />.<see cref="Data.Models.Data.Download.FileName" /> is not set by
|
/// To be called only when <see cref="Data.Models.Data.Download" />.<see cref="Data.Models.Data.Download.FileName" />
|
||||||
/// <see cref="GetDownloadInfos" />
|
/// is not set by
|
||||||
|
/// <see cref="GetDownloadInfos" />
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="download">The download to get the filename of</param>
|
/// <param name="download">The download to get the filename of</param>
|
||||||
/// <returns>The filename of the download</returns>
|
/// <returns>The filename of the download</returns>
|
||||||
Task<String> GetFileName(Download download);
|
Task<String> GetFileName(Download download);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,78 +3,19 @@ using Microsoft.Extensions.Logging;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using PremiumizeNET;
|
using PremiumizeNET;
|
||||||
using RdtClient.Data.Enums;
|
using RdtClient.Data.Enums;
|
||||||
|
using RdtClient.Data.Models.Data;
|
||||||
using RdtClient.Data.Models.TorrentClient;
|
using RdtClient.Data.Models.TorrentClient;
|
||||||
using RdtClient.Service.Helpers;
|
using RdtClient.Service.Helpers;
|
||||||
using RdtClient.Data.Models.Data;
|
|
||||||
using Torrent = RdtClient.Data.Models.Data.Torrent;
|
using Torrent = RdtClient.Data.Models.Data.Torrent;
|
||||||
|
|
||||||
namespace RdtClient.Service.Services.TorrentClients;
|
namespace RdtClient.Service.Services.TorrentClients;
|
||||||
|
|
||||||
public class PremiumizeTorrentClient(ILogger<PremiumizeTorrentClient> logger, IHttpClientFactory httpClientFactory, IDownloadableFileFilter fileFilter) : ITorrentClient
|
public class PremiumizeTorrentClient(ILogger<PremiumizeTorrentClient> logger, IHttpClientFactory httpClientFactory, IDownloadableFileFilter fileFilter) : ITorrentClient
|
||||||
{
|
{
|
||||||
private PremiumizeNETClient GetClient()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var apiKey = Settings.Get.Provider.ApiKey;
|
|
||||||
|
|
||||||
if (String.IsNullOrWhiteSpace(apiKey))
|
|
||||||
{
|
|
||||||
throw new("Premiumize API Key not set in the settings");
|
|
||||||
}
|
|
||||||
|
|
||||||
var httpClient = httpClientFactory.CreateClient();
|
|
||||||
httpClient.Timeout = TimeSpan.FromSeconds(10);
|
|
||||||
|
|
||||||
var premiumizeNetClient = new PremiumizeNETClient(apiKey, httpClient);
|
|
||||||
|
|
||||||
return premiumizeNetClient;
|
|
||||||
}
|
|
||||||
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
|
|
||||||
{
|
|
||||||
logger.LogError(ex, $"The connection to Premiumize has timed out: {ex.Message}");
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch (TaskCanceledException ex)
|
|
||||||
{
|
|
||||||
logger.LogError(ex, $"The connection to Premiumize has timed out: {ex.Message}");
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static TorrentClientTorrent Map(Transfer transfer)
|
|
||||||
{
|
|
||||||
return new()
|
|
||||||
{
|
|
||||||
Id = transfer.Id,
|
|
||||||
Filename = transfer.Name,
|
|
||||||
OriginalFilename = transfer.Name,
|
|
||||||
Hash = transfer.Src,
|
|
||||||
Bytes = 0,
|
|
||||||
OriginalBytes = 0,
|
|
||||||
Host = null,
|
|
||||||
Split = 0,
|
|
||||||
Progress = (Int64) ((transfer.Progress ?? 1.0) * 100.0),
|
|
||||||
Status = transfer.Status,
|
|
||||||
Message = transfer.Message,
|
|
||||||
StatusCode = 0,
|
|
||||||
Added = null,
|
|
||||||
Files = [],
|
|
||||||
Links =
|
|
||||||
[
|
|
||||||
transfer.FolderId
|
|
||||||
],
|
|
||||||
Ended = null,
|
|
||||||
Speed = 0,
|
|
||||||
Seeders = 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IList<TorrentClientTorrent>> GetTorrents()
|
public async Task<IList<TorrentClientTorrent>> GetTorrents()
|
||||||
{
|
{
|
||||||
var results = await GetClient().Transfers.ListAsync();
|
var results = await GetClient().Transfers.ListAsync();
|
||||||
|
|
||||||
return results.Select(Map).ToList();
|
return results.Select(Map).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -217,7 +158,7 @@ public class PremiumizeTorrentClient(ILogger<PremiumizeTorrentClient> logger, IH
|
||||||
Log($"Found transfer {transfer.Name} ({transfer.Id})", torrent);
|
Log($"Found transfer {transfer.Name} ({transfer.Id})", torrent);
|
||||||
|
|
||||||
var downloadInfos = await GetAllDownloadInfos(torrent, transfer.FolderId);
|
var downloadInfos = await GetAllDownloadInfos(torrent, transfer.FolderId);
|
||||||
|
|
||||||
if (!String.IsNullOrWhiteSpace(transfer.FileId))
|
if (!String.IsNullOrWhiteSpace(transfer.FileId))
|
||||||
{
|
{
|
||||||
var file = await GetClient().Items.DetailsAsync(transfer.FileId);
|
var file = await GetClient().Items.DetailsAsync(transfer.FileId);
|
||||||
|
|
@ -229,7 +170,11 @@ public class PremiumizeTorrentClient(ILogger<PremiumizeTorrentClient> logger, IH
|
||||||
Log($"File {file.Name} ({file.Id}) does not contain a link", torrent);
|
Log($"File {file.Name} ({file.Id}) does not contain a link", torrent);
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadInfos.Add(new () {RestrictedLink = file.Link, FileName = file.Name });
|
downloadInfos.Add(new()
|
||||||
|
{
|
||||||
|
RestrictedLink = file.Link,
|
||||||
|
FileName = file.Name
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var info in downloadInfos)
|
foreach (var info in downloadInfos)
|
||||||
|
|
@ -245,10 +190,70 @@ public class PremiumizeTorrentClient(ILogger<PremiumizeTorrentClient> logger, IH
|
||||||
{
|
{
|
||||||
// FileName is set in GetDownlaadInfos
|
// FileName is set in GetDownlaadInfos
|
||||||
Debug.Assert(download.FileName != null);
|
Debug.Assert(download.FileName != null);
|
||||||
|
|
||||||
return Task.FromResult(download.FileName);
|
return Task.FromResult(download.FileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PremiumizeNETClient GetClient()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var apiKey = Settings.Get.Provider.ApiKey;
|
||||||
|
|
||||||
|
if (String.IsNullOrWhiteSpace(apiKey))
|
||||||
|
{
|
||||||
|
throw new("Premiumize API Key not set in the settings");
|
||||||
|
}
|
||||||
|
|
||||||
|
var httpClient = httpClientFactory.CreateClient();
|
||||||
|
httpClient.Timeout = TimeSpan.FromSeconds(10);
|
||||||
|
|
||||||
|
var premiumizeNetClient = new PremiumizeNETClient(apiKey, httpClient);
|
||||||
|
|
||||||
|
return premiumizeNetClient;
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, $"The connection to Premiumize has timed out: {ex.Message}");
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException ex)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, $"The connection to Premiumize has timed out: {ex.Message}");
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TorrentClientTorrent Map(Transfer transfer)
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Id = transfer.Id,
|
||||||
|
Filename = transfer.Name,
|
||||||
|
OriginalFilename = transfer.Name,
|
||||||
|
Hash = transfer.Src,
|
||||||
|
Bytes = 0,
|
||||||
|
OriginalBytes = 0,
|
||||||
|
Host = null,
|
||||||
|
Split = 0,
|
||||||
|
Progress = (Int64)((transfer.Progress ?? 1.0) * 100.0),
|
||||||
|
Status = transfer.Status,
|
||||||
|
Message = transfer.Message,
|
||||||
|
StatusCode = 0,
|
||||||
|
Added = null,
|
||||||
|
Files = [],
|
||||||
|
Links =
|
||||||
|
[
|
||||||
|
transfer.FolderId
|
||||||
|
],
|
||||||
|
Ended = null,
|
||||||
|
Speed = 0,
|
||||||
|
Seeders = 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<TorrentClientTorrent> GetInfo(String id)
|
private async Task<TorrentClientTorrent> GetInfo(String id)
|
||||||
{
|
{
|
||||||
var results = await GetClient().Transfers.ListAsync();
|
var results = await GetClient().Transfers.ListAsync();
|
||||||
|
|
@ -259,7 +264,6 @@ public class PremiumizeTorrentClient(ILogger<PremiumizeTorrentClient> logger, IH
|
||||||
|
|
||||||
private async Task<List<DownloadInfo>> GetAllDownloadInfos(Torrent torrent, String folderId)
|
private async Task<List<DownloadInfo>> GetAllDownloadInfos(Torrent torrent, String folderId)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (String.IsNullOrWhiteSpace(folderId))
|
if (String.IsNullOrWhiteSpace(folderId))
|
||||||
{
|
{
|
||||||
return [];
|
return [];
|
||||||
|
|
@ -270,6 +274,7 @@ public class PremiumizeTorrentClient(ILogger<PremiumizeTorrentClient> logger, IH
|
||||||
if (folder.Content == null)
|
if (folder.Content == null)
|
||||||
{
|
{
|
||||||
Log($"Found no items in folder {folder.Name} ({folderId})", torrent);
|
Log($"Found no items in folder {folder.Name} ({folderId})", torrent);
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -295,7 +300,11 @@ public class PremiumizeTorrentClient(ILogger<PremiumizeTorrentClient> logger, IH
|
||||||
|
|
||||||
Log($"Found item {item.Name} in folder {folder.Name} ({folderId})", torrent);
|
Log($"Found item {item.Name} in folder {folder.Name} ({folderId})", torrent);
|
||||||
|
|
||||||
downloadInfos.Add(new () { RestrictedLink = item.Link, FileName = item.Name});
|
downloadInfos.Add(new()
|
||||||
|
{
|
||||||
|
RestrictedLink = item.Link,
|
||||||
|
FileName = item.Name
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else if (item.Type == "folder")
|
else if (item.Type == "folder")
|
||||||
{
|
{
|
||||||
|
|
@ -327,4 +336,4 @@ public class PremiumizeTorrentClient(ILogger<PremiumizeTorrentClient> logger, IH
|
||||||
|
|
||||||
logger.LogDebug(message);
|
logger.LogDebug(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,84 +15,6 @@ public class RealDebridTorrentClient(ILogger<RealDebridTorrentClient> logger, IH
|
||||||
{
|
{
|
||||||
private TimeSpan? _offset;
|
private TimeSpan? _offset;
|
||||||
|
|
||||||
private RdNetClient GetClient()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var apiKey = Settings.Get.Provider.ApiKey;
|
|
||||||
|
|
||||||
if (String.IsNullOrWhiteSpace(apiKey))
|
|
||||||
{
|
|
||||||
throw new("Real-Debrid API Key not set in the settings");
|
|
||||||
}
|
|
||||||
|
|
||||||
var httpClient = httpClientFactory.CreateClient(DiConfig.RD_CLIENT);
|
|
||||||
httpClient.Timeout = TimeSpan.FromSeconds(Settings.Get.Provider.Timeout);
|
|
||||||
|
|
||||||
var rdtNetClient = new RdNetClient(null, httpClient, 5, Settings.Get.Provider.ApiHostname);
|
|
||||||
rdtNetClient.UseApiAuthentication(apiKey);
|
|
||||||
|
|
||||||
// Get the server time to fix up the timezones on results
|
|
||||||
if (_offset == null)
|
|
||||||
{
|
|
||||||
var serverTime = rdtNetClient.Api.GetIsoTimeAsync().Result;
|
|
||||||
_offset = serverTime.Offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
return rdtNetClient;
|
|
||||||
}
|
|
||||||
catch (AggregateException ae)
|
|
||||||
{
|
|
||||||
foreach (var inner in ae.InnerExceptions)
|
|
||||||
{
|
|
||||||
logger.LogError(inner, $"The connection to RealDebrid has failed: {inner.Message}");
|
|
||||||
}
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
|
|
||||||
{
|
|
||||||
logger.LogError(ex, $"The connection to RealDebrid has timed out: {ex.Message}");
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch (TaskCanceledException ex)
|
|
||||||
{
|
|
||||||
logger.LogError(ex, $"The connection to RealDebrid has timed out: {ex.Message}");
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private TorrentClientTorrent Map(Torrent torrent)
|
|
||||||
{
|
|
||||||
return new()
|
|
||||||
{
|
|
||||||
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 ?? []).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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IList<TorrentClientTorrent>> GetTorrents()
|
public async Task<IList<TorrentClientTorrent>> GetTorrents()
|
||||||
{
|
{
|
||||||
var offset = 0;
|
var offset = 0;
|
||||||
|
|
@ -118,7 +40,7 @@ public class RealDebridTorrentClient(ILogger<RealDebridTorrentClient> logger, IH
|
||||||
public async Task<TorrentClientUser> GetUser()
|
public async Task<TorrentClientUser> GetUser()
|
||||||
{
|
{
|
||||||
var user = await GetClient().User.GetAsync();
|
var user = await GetClient().User.GetAsync();
|
||||||
|
|
||||||
return new()
|
return new()
|
||||||
{
|
{
|
||||||
Username = user.Username,
|
Username = user.Username,
|
||||||
|
|
@ -167,7 +89,6 @@ public class RealDebridTorrentClient(ILogger<RealDebridTorrentClient> logger, IH
|
||||||
files = [.. torrent.Files];
|
files = [.. torrent.Files];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
files = files.Where(f => fileFilter.IsDownloadable(torrent, f.Path, f.Bytes)).ToList();
|
files = files.Where(f => fileFilter.IsDownloadable(torrent, f.Path, f.Bytes)).ToList();
|
||||||
|
|
||||||
Log($"Selecting {files.Count}/{torrent.Files.Count} files", torrent);
|
Log($"Selecting {files.Count}/{torrent.Files.Count} files", torrent);
|
||||||
|
|
@ -310,8 +231,9 @@ public class RealDebridTorrentClient(ILogger<RealDebridTorrentClient> logger, IH
|
||||||
Log($"{link}", torrent);
|
Log($"{link}", torrent);
|
||||||
}
|
}
|
||||||
|
|
||||||
Log($"Torrent has {torrent.Files.Count(m => m.Selected)} selected files out of {torrent.Files.Count} files, found {downloadLinks.Count} links, torrent ended: {torrent.RdEnded}", torrent);
|
Log($"Torrent has {torrent.Files.Count(m => m.Selected)} selected files out of {torrent.Files.Count} files, found {downloadLinks.Count} links, torrent ended: {torrent.RdEnded}",
|
||||||
|
torrent);
|
||||||
|
|
||||||
// Check if all the links are set that have been selected
|
// Check if all the links are set that have been selected
|
||||||
if (torrent.Files.Count(m => m.Selected) == downloadLinks.Count)
|
if (torrent.Files.Count(m => m.Selected) == downloadLinks.Count)
|
||||||
{
|
{
|
||||||
|
|
@ -344,7 +266,7 @@ public class RealDebridTorrentClient(ILogger<RealDebridTorrentClient> logger, IH
|
||||||
}
|
}
|
||||||
|
|
||||||
Log($"Did not find any suiteable download links", torrent);
|
Log($"Did not find any suiteable download links", torrent);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -361,6 +283,85 @@ public class RealDebridTorrentClient(ILogger<RealDebridTorrentClient> logger, IH
|
||||||
return Task.FromResult(HttpUtility.UrlDecode(uri.Segments.Last()));
|
return Task.FromResult(HttpUtility.UrlDecode(uri.Segments.Last()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private RdNetClient GetClient()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var apiKey = Settings.Get.Provider.ApiKey;
|
||||||
|
|
||||||
|
if (String.IsNullOrWhiteSpace(apiKey))
|
||||||
|
{
|
||||||
|
throw new("Real-Debrid API Key not set in the settings");
|
||||||
|
}
|
||||||
|
|
||||||
|
var httpClient = httpClientFactory.CreateClient(DiConfig.RD_CLIENT);
|
||||||
|
httpClient.Timeout = TimeSpan.FromSeconds(Settings.Get.Provider.Timeout);
|
||||||
|
|
||||||
|
var rdtNetClient = new RdNetClient(null, httpClient, 5, Settings.Get.Provider.ApiHostname);
|
||||||
|
rdtNetClient.UseApiAuthentication(apiKey);
|
||||||
|
|
||||||
|
// Get the server time to fix up the timezones on results
|
||||||
|
if (_offset == null)
|
||||||
|
{
|
||||||
|
var serverTime = rdtNetClient.Api.GetIsoTimeAsync().Result;
|
||||||
|
_offset = serverTime.Offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rdtNetClient;
|
||||||
|
}
|
||||||
|
catch (AggregateException ae)
|
||||||
|
{
|
||||||
|
foreach (var inner in ae.InnerExceptions)
|
||||||
|
{
|
||||||
|
logger.LogError(inner, $"The connection to RealDebrid has failed: {inner.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, $"The connection to RealDebrid has timed out: {ex.Message}");
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException ex)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, $"The connection to RealDebrid has timed out: {ex.Message}");
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TorrentClientTorrent Map(Torrent torrent)
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
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 ?? []).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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private DateTimeOffset? ChangeTimeZone(DateTimeOffset? dateTimeOffset)
|
private DateTimeOffset? ChangeTimeZone(DateTimeOffset? dateTimeOffset)
|
||||||
{
|
{
|
||||||
if (_offset == null)
|
if (_offset == null)
|
||||||
|
|
@ -387,4 +388,4 @@ public class RealDebridTorrentClient(ILogger<RealDebridTorrentClient> logger, IH
|
||||||
|
|
||||||
logger.LogDebug(message);
|
logger.LogDebug(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,106 +1,33 @@
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using MonoTorrent;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using TorBoxNET;
|
|
||||||
using RdtClient.Data.Enums;
|
using RdtClient.Data.Enums;
|
||||||
using RdtClient.Data.Models.TorrentClient;
|
|
||||||
using RdtClient.Data.Models.Data;
|
using RdtClient.Data.Models.Data;
|
||||||
|
using RdtClient.Data.Models.TorrentClient;
|
||||||
using RdtClient.Service.Helpers;
|
using RdtClient.Service.Helpers;
|
||||||
|
using TorBoxNET;
|
||||||
|
using Torrent = RdtClient.Data.Models.Data.Torrent;
|
||||||
|
|
||||||
namespace RdtClient.Service.Services.TorrentClients;
|
namespace RdtClient.Service.Services.TorrentClients;
|
||||||
|
|
||||||
public class TorBoxTorrentClient(ILogger<TorBoxTorrentClient> logger, IHttpClientFactory httpClientFactory, IDownloadableFileFilter fileFilter) : ITorrentClient
|
public class TorBoxTorrentClient(ILogger<TorBoxTorrentClient> logger, IHttpClientFactory httpClientFactory, IDownloadableFileFilter fileFilter) : ITorrentClient
|
||||||
{
|
{
|
||||||
private TimeSpan? _offset;
|
private TimeSpan? _offset;
|
||||||
private TorBoxNetClient GetClient()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var apiKey = Settings.Get.Provider.ApiKey;
|
|
||||||
|
|
||||||
if (String.IsNullOrWhiteSpace(apiKey))
|
|
||||||
{
|
|
||||||
throw new("TorBox API Key not set in the settings");
|
|
||||||
}
|
|
||||||
|
|
||||||
var httpClient = httpClientFactory.CreateClient();
|
|
||||||
httpClient.Timeout = TimeSpan.FromSeconds(Settings.Get.Provider.Timeout);
|
|
||||||
|
|
||||||
var torBoxNetClient = new TorBoxNetClient(null, httpClient, 5);
|
|
||||||
torBoxNetClient.UseApiAuthentication(apiKey);
|
|
||||||
|
|
||||||
// Get the server time to fix up the timezones on results
|
|
||||||
if (_offset == null)
|
|
||||||
{
|
|
||||||
var serverTime = DateTimeOffset.UtcNow;
|
|
||||||
_offset = serverTime.Offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
return torBoxNetClient;
|
|
||||||
}
|
|
||||||
catch (AggregateException ae)
|
|
||||||
{
|
|
||||||
foreach (var inner in ae.InnerExceptions)
|
|
||||||
{
|
|
||||||
logger.LogError(inner, $"The connection to TorBox has failed: {inner.Message}");
|
|
||||||
}
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
|
|
||||||
{
|
|
||||||
logger.LogError(ex, $"The connection to TorBox has timed out: {ex.Message}");
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch (TaskCanceledException ex)
|
|
||||||
{
|
|
||||||
logger.LogError(ex, $"The connection to TorBox has timed out: {ex.Message}");
|
|
||||||
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private TorrentClientTorrent Map(TorrentInfoResult torrent)
|
|
||||||
{
|
|
||||||
return new()
|
|
||||||
{
|
|
||||||
Id = torrent.Hash,
|
|
||||||
Filename = torrent.Name,
|
|
||||||
OriginalFilename = torrent.Name,
|
|
||||||
Hash = torrent.Hash,
|
|
||||||
Bytes = torrent.Size,
|
|
||||||
OriginalBytes = torrent.Size,
|
|
||||||
Host = torrent.DownloadPresent.ToString(),
|
|
||||||
Split = 0,
|
|
||||||
Progress = (Int64)(torrent.Progress * 100.0),
|
|
||||||
Status = torrent.DownloadState,
|
|
||||||
Added = ChangeTimeZone(torrent.CreatedAt)!.Value,
|
|
||||||
Files = (torrent.Files ?? []).Select(m => new TorrentClientFile
|
|
||||||
{
|
|
||||||
Path = String.Join("/", m.Name.Split('/').Skip(1)),
|
|
||||||
Bytes = m.Size,
|
|
||||||
Id = m.Id,
|
|
||||||
Selected = true
|
|
||||||
}).ToList(),
|
|
||||||
Links = [],
|
|
||||||
Ended = ChangeTimeZone(torrent.UpdatedAt),
|
|
||||||
Speed = torrent.DownloadSpeed,
|
|
||||||
Seeders = torrent.Seeds,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IList<TorrentClientTorrent>> GetTorrents()
|
public async Task<IList<TorrentClientTorrent>> GetTorrents()
|
||||||
{
|
{
|
||||||
var torrents = new List<TorrentInfoResult>();
|
var torrents = new List<TorrentInfoResult>();
|
||||||
|
|
||||||
var currentTorrents = await GetClient().Torrents.GetCurrentAsync(true);
|
var currentTorrents = await GetClient().Torrents.GetCurrentAsync(true);
|
||||||
|
|
||||||
if (currentTorrents != null)
|
if (currentTorrents != null)
|
||||||
{
|
{
|
||||||
torrents.AddRange(currentTorrents);
|
torrents.AddRange(currentTorrents);
|
||||||
}
|
}
|
||||||
|
|
||||||
var queuedTorrents = await GetClient().Torrents.GetQueuedAsync(true);
|
var queuedTorrents = await GetClient().Torrents.GetQueuedAsync(true);
|
||||||
|
|
||||||
if (queuedTorrents != null)
|
if (queuedTorrents != null)
|
||||||
{
|
{
|
||||||
torrents.AddRange(queuedTorrents);
|
torrents.AddRange(queuedTorrents);
|
||||||
|
|
@ -124,11 +51,12 @@ public class TorBoxTorrentClient(ILogger<TorBoxTorrentClient> logger, IHttpClien
|
||||||
{
|
{
|
||||||
var user = await GetClient().User.GetAsync(true);
|
var user = await GetClient().User.GetAsync(true);
|
||||||
|
|
||||||
var result = await GetClient().Torrents.AddMagnetAsync(magnetLink, user.Data?.Settings?.SeedTorrents ?? 3, false);
|
var result = await GetClient().Torrents.AddMagnetAsync(magnetLink, user.Data?.Settings?.SeedTorrents ?? 3);
|
||||||
|
|
||||||
if (result.Error == "ACTIVE_LIMIT")
|
if (result.Error == "ACTIVE_LIMIT")
|
||||||
{
|
{
|
||||||
var magnetLinkInfo = MonoTorrent.MagnetLink.Parse(magnetLink);
|
var magnetLinkInfo = MagnetLink.Parse(magnetLink);
|
||||||
|
|
||||||
return magnetLinkInfo.InfoHashes.V1!.ToHex().ToLowerInvariant();
|
return magnetLinkInfo.InfoHashes.V1!.ToHex().ToLowerInvariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -140,11 +68,13 @@ public class TorBoxTorrentClient(ILogger<TorBoxTorrentClient> logger, IHttpClien
|
||||||
var user = await GetClient().User.GetAsync(true);
|
var user = await GetClient().User.GetAsync(true);
|
||||||
|
|
||||||
var result = await GetClient().Torrents.AddFileAsync(bytes, user.Data?.Settings?.SeedTorrents ?? 3);
|
var result = await GetClient().Torrents.AddFileAsync(bytes, user.Data?.Settings?.SeedTorrents ?? 3);
|
||||||
|
|
||||||
if (result.Error == "ACTIVE_LIMIT")
|
if (result.Error == "ACTIVE_LIMIT")
|
||||||
{
|
{
|
||||||
using var stream = new MemoryStream(bytes);
|
using var stream = new MemoryStream(bytes);
|
||||||
|
|
||||||
var torrent = await MonoTorrent.Torrent.LoadAsync(stream);
|
var torrent = await MonoTorrent.Torrent.LoadAsync(stream);
|
||||||
|
|
||||||
return torrent.InfoHashes.V1!.ToHex().ToLowerInvariant();
|
return torrent.InfoHashes.V1!.ToHex().ToLowerInvariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -153,15 +83,16 @@ public class TorBoxTorrentClient(ILogger<TorBoxTorrentClient> logger, IHttpClien
|
||||||
|
|
||||||
public async Task<IList<TorrentClientAvailableFile>> GetAvailableFiles(String hash)
|
public async Task<IList<TorrentClientAvailableFile>> GetAvailableFiles(String hash)
|
||||||
{
|
{
|
||||||
var availability = await GetClient().Torrents.GetAvailabilityAsync(hash, listFiles: true);
|
var availability = await GetClient().Torrents.GetAvailabilityAsync(hash, true);
|
||||||
|
|
||||||
if (availability.Data != null && availability.Data.Count > 0)
|
if (availability.Data != null && availability.Data.Count > 0)
|
||||||
{
|
{
|
||||||
return (availability.Data[0]?.Files ?? []).Select(file => new TorrentClientAvailableFile
|
return (availability.Data[0]?.Files ?? []).Select(file => new TorrentClientAvailableFile
|
||||||
{
|
{
|
||||||
Filename = file.Name,
|
Filename = file.Name,
|
||||||
Filesize = file.Size
|
Filesize = file.Size
|
||||||
}).ToList();
|
})
|
||||||
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
|
|
@ -265,7 +196,6 @@ public class TorBoxTorrentClient(ILogger<TorBoxTorrentClient> logger, IHttpClien
|
||||||
_ => TorrentStatus.Error
|
_ => TorrentStatus.Error
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -284,7 +214,7 @@ public class TorBoxTorrentClient(ILogger<TorBoxTorrentClient> logger, IHttpClien
|
||||||
|
|
||||||
public async Task<IList<DownloadInfo>?> GetDownloadInfos(Torrent torrent)
|
public async Task<IList<DownloadInfo>?> GetDownloadInfos(Torrent torrent)
|
||||||
{
|
{
|
||||||
var torrentId = await GetClient().Torrents.GetHashInfoAsync(torrent.Hash, skipCache: true);
|
var torrentId = await GetClient().Torrents.GetHashInfoAsync(torrent.Hash, true);
|
||||||
var downloadableFiles = torrent.Files.Where(file => fileFilter.IsDownloadable(torrent, file.Path, file.Bytes)).ToList();
|
var downloadableFiles = torrent.Files.Where(file => fileFilter.IsDownloadable(torrent, file.Path, file.Bytes)).ToList();
|
||||||
|
|
||||||
if (downloadableFiles.Count == torrent.Files.Count && torrent.DownloadClient != Data.Enums.DownloadClient.Symlink && Settings.Get.Provider.PreferZippedDownloads)
|
if (downloadableFiles.Count == torrent.Files.Count && torrent.DownloadClient != Data.Enums.DownloadClient.Symlink && Settings.Get.Provider.PreferZippedDownloads)
|
||||||
|
|
@ -316,10 +246,89 @@ public class TorBoxTorrentClient(ILogger<TorBoxTorrentClient> logger, IHttpClien
|
||||||
{
|
{
|
||||||
// FileName is set in GetDownlaadInfos
|
// FileName is set in GetDownlaadInfos
|
||||||
Debug.Assert(download.FileName != null);
|
Debug.Assert(download.FileName != null);
|
||||||
|
|
||||||
return Task.FromResult(download.FileName);
|
return Task.FromResult(download.FileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private TorBoxNetClient GetClient()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var apiKey = Settings.Get.Provider.ApiKey;
|
||||||
|
|
||||||
|
if (String.IsNullOrWhiteSpace(apiKey))
|
||||||
|
{
|
||||||
|
throw new("TorBox API Key not set in the settings");
|
||||||
|
}
|
||||||
|
|
||||||
|
var httpClient = httpClientFactory.CreateClient();
|
||||||
|
httpClient.Timeout = TimeSpan.FromSeconds(Settings.Get.Provider.Timeout);
|
||||||
|
|
||||||
|
var torBoxNetClient = new TorBoxNetClient(null, httpClient, 5);
|
||||||
|
torBoxNetClient.UseApiAuthentication(apiKey);
|
||||||
|
|
||||||
|
// Get the server time to fix up the timezones on results
|
||||||
|
if (_offset == null)
|
||||||
|
{
|
||||||
|
var serverTime = DateTimeOffset.UtcNow;
|
||||||
|
_offset = serverTime.Offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
return torBoxNetClient;
|
||||||
|
}
|
||||||
|
catch (AggregateException ae)
|
||||||
|
{
|
||||||
|
foreach (var inner in ae.InnerExceptions)
|
||||||
|
{
|
||||||
|
logger.LogError(inner, $"The connection to TorBox has failed: {inner.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, $"The connection to TorBox has timed out: {ex.Message}");
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException ex)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, $"The connection to TorBox has timed out: {ex.Message}");
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TorrentClientTorrent Map(TorrentInfoResult torrent)
|
||||||
|
{
|
||||||
|
return new()
|
||||||
|
{
|
||||||
|
Id = torrent.Hash,
|
||||||
|
Filename = torrent.Name,
|
||||||
|
OriginalFilename = torrent.Name,
|
||||||
|
Hash = torrent.Hash,
|
||||||
|
Bytes = torrent.Size,
|
||||||
|
OriginalBytes = torrent.Size,
|
||||||
|
Host = torrent.DownloadPresent.ToString(),
|
||||||
|
Split = 0,
|
||||||
|
Progress = (Int64)(torrent.Progress * 100.0),
|
||||||
|
Status = torrent.DownloadState,
|
||||||
|
Added = ChangeTimeZone(torrent.CreatedAt)!.Value,
|
||||||
|
Files = (torrent.Files ?? []).Select(m => new TorrentClientFile
|
||||||
|
{
|
||||||
|
Path = String.Join("/", m.Name.Split('/').Skip(1)),
|
||||||
|
Bytes = m.Size,
|
||||||
|
Id = m.Id,
|
||||||
|
Selected = true
|
||||||
|
})
|
||||||
|
.ToList(),
|
||||||
|
Links = [],
|
||||||
|
Ended = ChangeTimeZone(torrent.UpdatedAt),
|
||||||
|
Speed = torrent.DownloadSpeed,
|
||||||
|
Seeders = torrent.Seeds
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private DateTimeOffset? ChangeTimeZone(DateTimeOffset? dateTimeOffset)
|
private DateTimeOffset? ChangeTimeZone(DateTimeOffset? dateTimeOffset)
|
||||||
{
|
{
|
||||||
if (_offset == null)
|
if (_offset == null)
|
||||||
|
|
@ -332,7 +341,7 @@ public class TorBoxTorrentClient(ILogger<TorBoxTorrentClient> logger, IHttpClien
|
||||||
|
|
||||||
private async Task<TorrentClientTorrent> GetInfo(String torrentHash)
|
private async Task<TorrentClientTorrent> GetInfo(String torrentHash)
|
||||||
{
|
{
|
||||||
var result = await GetClient().Torrents.GetHashInfoAsync(torrentHash, skipCache: true);
|
var result = await GetClient().Torrents.GetHashInfoAsync(torrentHash, true);
|
||||||
|
|
||||||
return Map(result!);
|
return Map(result!);
|
||||||
}
|
}
|
||||||
|
|
@ -346,6 +355,7 @@ public class TorBoxTorrentClient(ILogger<TorBoxTorrentClient> logger, IHttpClien
|
||||||
var innerFolder = Directory.GetDirectories(hashDir)[0];
|
var innerFolder = Directory.GetDirectories(hashDir)[0];
|
||||||
|
|
||||||
var moveDir = extractPath;
|
var moveDir = extractPath;
|
||||||
|
|
||||||
if (!extractPath.EndsWith(_torrent.RdName!))
|
if (!extractPath.EndsWith(_torrent.RdName!))
|
||||||
{
|
{
|
||||||
moveDir = hashDir;
|
moveDir = hashDir;
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
using Aria2NET;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Aria2NET;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using RdtClient.Data.Enums;
|
using RdtClient.Data.Enums;
|
||||||
using RdtClient.Data.Models.Data;
|
using RdtClient.Data.Models.Data;
|
||||||
using RdtClient.Data.Models.Internal;
|
using RdtClient.Data.Models.Internal;
|
||||||
using RdtClient.Service.Helpers;
|
using RdtClient.Service.Helpers;
|
||||||
using RdtClient.Service.Services.Downloaders;
|
using RdtClient.Service.Services.Downloaders;
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Text.Json;
|
|
||||||
|
|
||||||
namespace RdtClient.Service.Services;
|
namespace RdtClient.Service.Services;
|
||||||
|
|
||||||
|
|
@ -16,13 +16,13 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
|
||||||
public static readonly ConcurrentDictionary<Guid, DownloadClient> ActiveDownloadClients = new();
|
public static readonly ConcurrentDictionary<Guid, DownloadClient> ActiveDownloadClients = new();
|
||||||
public static readonly ConcurrentDictionary<Guid, UnpackClient> ActiveUnpackClients = new();
|
public static readonly ConcurrentDictionary<Guid, UnpackClient> ActiveUnpackClients = new();
|
||||||
|
|
||||||
public static Boolean IsPausedForLowDiskSpace { get; set; }
|
|
||||||
|
|
||||||
private readonly HttpClient _httpClient = new()
|
private readonly HttpClient _httpClient = new()
|
||||||
{
|
{
|
||||||
Timeout = TimeSpan.FromSeconds(10)
|
Timeout = TimeSpan.FromSeconds(10)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public static Boolean IsPausedForLowDiskSpace { get; set; }
|
||||||
|
|
||||||
public async Task Initialize()
|
public async Task Initialize()
|
||||||
{
|
{
|
||||||
Log("Initializing TorrentRunner");
|
Log("Initializing TorrentRunner");
|
||||||
|
|
@ -73,25 +73,30 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
|
||||||
if (String.IsNullOrWhiteSpace(Settings.Get.Provider.ApiKey))
|
if (String.IsNullOrWhiteSpace(Settings.Get.Provider.ApiKey))
|
||||||
{
|
{
|
||||||
Log($"No RealDebridApiKey set in settings");
|
Log($"No RealDebridApiKey set in settings");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var settingDownloadLimit = Settings.Get.General.DownloadLimit;
|
var settingDownloadLimit = Settings.Get.General.DownloadLimit;
|
||||||
|
|
||||||
if (settingDownloadLimit < 1)
|
if (settingDownloadLimit < 1)
|
||||||
{
|
{
|
||||||
settingDownloadLimit = 1;
|
settingDownloadLimit = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
var settingUnpackLimit = Settings.Get.General.UnpackLimit;
|
var settingUnpackLimit = Settings.Get.General.UnpackLimit;
|
||||||
|
|
||||||
if (settingUnpackLimit < 0)
|
if (settingUnpackLimit < 0)
|
||||||
{
|
{
|
||||||
settingUnpackLimit = 0;
|
settingUnpackLimit = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
var settingDownloadPath = Settings.Get.DownloadClient.DownloadPath;
|
var settingDownloadPath = Settings.Get.DownloadClient.DownloadPath;
|
||||||
|
|
||||||
if (String.IsNullOrWhiteSpace(settingDownloadPath))
|
if (String.IsNullOrWhiteSpace(settingDownloadPath))
|
||||||
{
|
{
|
||||||
logger.LogError("No DownloadPath set in settings");
|
logger.LogError("No DownloadPath set in settings");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -163,7 +168,10 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
|
||||||
{
|
{
|
||||||
// Retry the download if an error is encountered.
|
// Retry the download if an error is encountered.
|
||||||
LogError($"Download reported an error: {downloadClient.Error}", download, download.Torrent);
|
LogError($"Download reported an error: {downloadClient.Error}", download, download.Torrent);
|
||||||
Log($"Download retry count {download.RetryCount}/{download.Torrent!.DownloadRetryAttempts}, torrent retry count {download.Torrent.RetryCount}/{download.Torrent.TorrentRetryAttempts}", download, download.Torrent);
|
|
||||||
|
Log($"Download retry count {download.RetryCount}/{download.Torrent!.DownloadRetryAttempts}, torrent retry count {download.Torrent.RetryCount}/{download.Torrent.TorrentRetryAttempts}",
|
||||||
|
download,
|
||||||
|
download.Torrent);
|
||||||
|
|
||||||
if (download.RetryCount < download.Torrent.DownloadRetryAttempts)
|
if (download.RetryCount < download.Torrent.DownloadRetryAttempts)
|
||||||
{
|
{
|
||||||
|
|
@ -246,6 +254,7 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
|
||||||
{
|
{
|
||||||
await activeDownload.Value.Cancel();
|
await activeDownload.Value.Cancel();
|
||||||
ActiveDownloadClients.TryRemove(activeDownload.Key, out _);
|
ActiveDownloadClients.TryRemove(activeDownload.Key, out _);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -258,6 +267,7 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
|
||||||
{
|
{
|
||||||
activeUnpacks.Value.Cancel();
|
activeUnpacks.Value.Cancel();
|
||||||
ActiveUnpackClients.TryRemove(activeUnpacks.Key, out _);
|
ActiveUnpackClients.TryRemove(activeUnpacks.Key, out _);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -273,6 +283,7 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
|
||||||
{
|
{
|
||||||
await torrents.UpdateRetry(torrent.TorrentId, null, torrent.RetryCount);
|
await torrents.UpdateRetry(torrent.TorrentId, null, torrent.RetryCount);
|
||||||
Log($"Torrent reach max retry count");
|
Log($"Torrent reach max retry count");
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -567,14 +578,14 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we have reached the download limit, if so queue the download, but don't start it.
|
// Check if we have reached the download limit, if so queue the download, but don't start it.
|
||||||
if (TorrentRunner.ActiveUnpackClients.Count >= settingUnpackLimit)
|
if (ActiveUnpackClients.Count >= settingUnpackLimit)
|
||||||
{
|
{
|
||||||
Log($"Not starting unpack because there are already the max number of unpacks active", download, torrent);
|
Log($"Not starting unpack because there are already the max number of unpacks active", download, torrent);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TorrentRunner.ActiveUnpackClients.ContainsKey(download.DownloadId))
|
if (ActiveUnpackClients.ContainsKey(download.DownloadId))
|
||||||
{
|
{
|
||||||
Log($"Not starting unpack because this download is already active", download, torrent);
|
Log($"Not starting unpack because this download is already active", download, torrent);
|
||||||
|
|
||||||
|
|
@ -596,7 +607,7 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
|
||||||
// Start the unpacking process
|
// Start the unpacking process
|
||||||
var unpackClient = new UnpackClient(download, downloadPath);
|
var unpackClient = new UnpackClient(download, downloadPath);
|
||||||
|
|
||||||
if (TorrentRunner.ActiveUnpackClients.TryAdd(download.DownloadId, unpackClient))
|
if (ActiveUnpackClients.TryAdd(download.DownloadId, unpackClient))
|
||||||
{
|
{
|
||||||
Log($"Starting unpack", download, torrent);
|
Log($"Starting unpack", download, torrent);
|
||||||
|
|
||||||
|
|
@ -647,8 +658,8 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if torrent is complete, or if we don't want to download any files to the host.
|
// Check if torrent is complete, or if we don't want to download any files to the host.
|
||||||
if ((torrent.Downloads.Count > 0) ||
|
if (torrent.Downloads.Count > 0 ||
|
||||||
torrent.RdStatus == TorrentStatus.Finished && torrent.HostDownloadAction == TorrentHostDownloadAction.DownloadNone)
|
(torrent.RdStatus == TorrentStatus.Finished && torrent.HostDownloadAction == TorrentHostDownloadAction.DownloadNone))
|
||||||
{
|
{
|
||||||
var completeCount = torrent.Downloads.Count(m => m.Completed != null);
|
var completeCount = torrent.Downloads.Count(m => m.Completed != null);
|
||||||
|
|
||||||
|
|
@ -659,7 +670,7 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
|
||||||
|
|
||||||
if (totalDownloadBytes > 0)
|
if (totalDownloadBytes > 0)
|
||||||
{
|
{
|
||||||
completePerc = (Int32)((Double)totalDoneBytes / totalDownloadBytes * 100);
|
completePerc = (Int32)(((Double)totalDoneBytes / totalDownloadBytes) * 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (completeCount == torrent.Downloads.Count)
|
if (completeCount == torrent.Downloads.Count)
|
||||||
|
|
@ -737,4 +748,4 @@ public class TorrentRunner(ILogger<TorrentRunner> logger, Torrents torrents, Dow
|
||||||
|
|
||||||
logger.LogError(message);
|
logger.LogError(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,8 @@ public class Torrents(
|
||||||
ReferenceHandler = ReferenceHandler.IgnoreCycles
|
ReferenceHandler = ReferenceHandler.IgnoreCycles
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static readonly SemaphoreSlim TorrentResetLock = new(1, 1);
|
||||||
|
|
||||||
private ITorrentClient TorrentClient
|
private ITorrentClient TorrentClient
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
@ -54,8 +56,6 @@ public class Torrents(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly SemaphoreSlim TorrentResetLock = new(1, 1);
|
|
||||||
|
|
||||||
public async Task<IList<Torrent>> Get()
|
public async Task<IList<Torrent>> Get()
|
||||||
{
|
{
|
||||||
var torrents = await torrentData.Get();
|
var torrents = await torrentData.Get();
|
||||||
|
|
@ -112,6 +112,7 @@ public class Torrents(
|
||||||
{
|
{
|
||||||
var enriched = await enricher.EnrichMagnetLink(magnetLink);
|
var enriched = await enricher.EnrichMagnetLink(magnetLink);
|
||||||
MagnetLink magnet;
|
MagnetLink magnet;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
magnet = MagnetLink.Parse(magnetLink);
|
magnet = MagnetLink.Parse(magnetLink);
|
||||||
|
|
@ -119,6 +120,7 @@ public class Torrents(
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
logger.LogError(ex, "{ex.Message}, trying to parse {magnetLink}", ex.Message, magnetLink);
|
logger.LogError(ex, "{ex.Message}, trying to parse {magnetLink}", ex.Message, magnetLink);
|
||||||
|
|
||||||
throw new($"{ex.Message}, trying to parse {magnetLink}");
|
throw new($"{ex.Message}, trying to parse {magnetLink}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -142,6 +144,7 @@ public class Torrents(
|
||||||
if (bannedUrls.Count > 0)
|
if (bannedUrls.Count > 0)
|
||||||
{
|
{
|
||||||
var bannedUrlsString = String.Join(", ", bannedUrls);
|
var bannedUrlsString = String.Join(", ", bannedUrls);
|
||||||
|
|
||||||
throw new($"Cannot add torrent, the torrent contains banned trackers: {bannedUrlsString}.");
|
throw new($"Cannot add torrent, the torrent contains banned trackers: {bannedUrlsString}.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -213,12 +216,13 @@ public class Torrents(
|
||||||
if (bannedUrls.Count > 0)
|
if (bannedUrls.Count > 0)
|
||||||
{
|
{
|
||||||
var bannedUrlsString = String.Join(", ", bannedUrls);
|
var bannedUrlsString = String.Join(", ", bannedUrls);
|
||||||
|
|
||||||
throw new($"Cannot add torrent, the torrent contains banned trackers: {bannedUrlsString}.");
|
throw new($"Cannot add torrent, the torrent contains banned trackers: {bannedUrlsString}.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
torrent.RdStatus = TorrentStatus.Queued;
|
torrent.RdStatus = TorrentStatus.Queued;
|
||||||
torrent.RdName = monoTorrent.Name;
|
torrent.RdName = monoTorrent.Name;
|
||||||
|
|
||||||
|
|
@ -262,9 +266,11 @@ public class Torrents(
|
||||||
{
|
{
|
||||||
case String magnetLink:
|
case String magnetLink:
|
||||||
await File.WriteAllTextAsync(copyFileName, magnetLink);
|
await File.WriteAllTextAsync(copyFileName, magnetLink);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case Byte[] torrentFile:
|
case Byte[] torrentFile:
|
||||||
await File.WriteAllBytesAsync(copyFileName, torrentFile);
|
await File.WriteAllBytesAsync(copyFileName, torrentFile);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -276,7 +282,7 @@ public class Torrents(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds torrent in database to debrid provider and updates database accordingly.
|
/// Adds torrent in database to debrid provider and updates database accordingly.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="torrent">The torrent from the database to upload to the debrid provider</param>
|
/// <param name="torrent">The torrent from the database to upload to the debrid provider</param>
|
||||||
/// <returns>Updated torrent</returns>
|
/// <returns>Updated torrent</returns>
|
||||||
|
|
@ -373,7 +379,7 @@ public class Torrents(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Logs a message to the console, sets the error on the torrent and ensures it is not retried.
|
/// Logs a message to the console, sets the error on the torrent and ensures it is not retried.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="torrent">The torrent to mark as "All files excluded"</param>
|
/// <param name="torrent">The torrent to mark as "All files excluded"</param>
|
||||||
private async Task MarkAllFilesExcluded(Torrent torrent)
|
private async Task MarkAllFilesExcluded(Torrent torrent)
|
||||||
|
|
@ -511,8 +517,9 @@ public class Torrents(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// To be called only when <see cref="Data.Models.Data.Download" />.<see cref="Data.Models.Data.Download.FileName" /> is not set by
|
/// To be called only when <see cref="Data.Models.Data.Download" />.<see cref="Data.Models.Data.Download.FileName" />
|
||||||
/// <see cref="ITorrentClient.GetDownloadInfos" />
|
/// is not set by
|
||||||
|
/// <see cref="ITorrentClient.GetDownloadInfos" />
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task<String> RetrieveFileName(Guid downloadId)
|
public async Task<String> RetrieveFileName(Guid downloadId)
|
||||||
{
|
{
|
||||||
|
|
@ -566,7 +573,8 @@ public class Torrents(
|
||||||
{
|
{
|
||||||
Category = Settings.Get.Provider.Default.Category,
|
Category = Settings.Get.Provider.Default.Category,
|
||||||
DownloadClient = Settings.Get.DownloadClient.Client,
|
DownloadClient = Settings.Get.DownloadClient.Client,
|
||||||
DownloadAction = Settings.Get.Provider.Default.OnlyDownloadAvailableFiles ? TorrentDownloadAction.DownloadAvailableFiles : TorrentDownloadAction.DownloadAll,
|
DownloadAction =
|
||||||
|
Settings.Get.Provider.Default.OnlyDownloadAvailableFiles ? TorrentDownloadAction.DownloadAvailableFiles : TorrentDownloadAction.DownloadAll,
|
||||||
HostDownloadAction = Settings.Get.Provider.Default.HostDownloadAction,
|
HostDownloadAction = Settings.Get.Provider.Default.HostDownloadAction,
|
||||||
FinishedActionDelay = Settings.Get.Provider.Default.FinishedActionDelay,
|
FinishedActionDelay = Settings.Get.Provider.Default.FinishedActionDelay,
|
||||||
FinishedAction = Settings.Get.Provider.Default.FinishedAction,
|
FinishedAction = Settings.Get.Provider.Default.FinishedAction,
|
||||||
|
|
@ -887,6 +895,7 @@ public class Torrents(
|
||||||
|
|
||||||
outputSb.AppendLine(data.Trim());
|
outputSb.AppendLine(data.Trim());
|
||||||
};
|
};
|
||||||
|
|
||||||
process.ErrorDataReceived += (_, data) =>
|
process.ErrorDataReceived += (_, data) =>
|
||||||
{
|
{
|
||||||
if (data == null)
|
if (data == null)
|
||||||
|
|
@ -967,4 +976,4 @@ public class Torrents(
|
||||||
|
|
||||||
logger.LogDebug(message);
|
logger.LogDebug(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
|
using System.Reflection;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
namespace RdtClient.Service.Services;
|
namespace RdtClient.Service.Services;
|
||||||
|
|
||||||
|
|
@ -8,10 +8,10 @@ public class TrackerListGrabber(IHttpClientFactory httpClientFactory, IMemoryCac
|
||||||
{
|
{
|
||||||
private const String CacheKey = "TrackerList";
|
private const String CacheKey = "TrackerList";
|
||||||
|
|
||||||
private Int32? _lastExpirationMinutes;
|
|
||||||
|
|
||||||
private static readonly SemaphoreSlim Semaphore = new(1, 1);
|
private static readonly SemaphoreSlim Semaphore = new(1, 1);
|
||||||
|
|
||||||
|
private Int32? _lastExpirationMinutes;
|
||||||
|
|
||||||
public async Task<String[]> GetTrackers()
|
public async Task<String[]> GetTrackers()
|
||||||
{
|
{
|
||||||
var trackerUrlList = Settings.Get.General.TrackerEnrichmentList;
|
var trackerUrlList = Settings.Get.General.TrackerEnrichmentList;
|
||||||
|
|
@ -148,6 +148,7 @@ public class TrackerListGrabber(IHttpClientFactory httpClientFactory, IMemoryCac
|
||||||
{
|
{
|
||||||
logger.LogDebug("Rejected tracker: {TrackerUrl} - Reason: Invalid format or unsupported scheme.", t);
|
logger.LogDebug("Rejected tracker: {TrackerUrl} - Reason: Invalid format or unsupported scheme.", t);
|
||||||
trackerRejectionCount++;
|
trackerRejectionCount++;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using RdtClient.Data.Models.Data;
|
using RdtClient.Data.Enums;
|
||||||
|
using RdtClient.Data.Models.Data;
|
||||||
using RdtClient.Service.Helpers;
|
using RdtClient.Service.Helpers;
|
||||||
using RdtClient.Service.Services.TorrentClients;
|
using RdtClient.Service.Services.TorrentClients;
|
||||||
using SharpCompress.Archives;
|
using SharpCompress.Archives;
|
||||||
|
|
@ -10,16 +11,15 @@ namespace RdtClient.Service.Services;
|
||||||
|
|
||||||
public class UnpackClient(Download download, String destinationPath)
|
public class UnpackClient(Download download, String destinationPath)
|
||||||
{
|
{
|
||||||
|
private readonly CancellationTokenSource _cancellationTokenSource = new();
|
||||||
|
|
||||||
|
private readonly Torrent _torrent = download.Torrent ?? throw new($"Torrent is null");
|
||||||
public Boolean Finished { get; private set; }
|
public Boolean Finished { get; private set; }
|
||||||
|
|
||||||
public String? Error { get; private set; }
|
public String? Error { get; private set; }
|
||||||
|
|
||||||
public Int32 Progess { get; private set; }
|
public Int32 Progess { get; private set; }
|
||||||
|
|
||||||
private readonly Torrent _torrent = download.Torrent ?? throw new($"Torrent is null");
|
|
||||||
|
|
||||||
private readonly CancellationTokenSource _cancellationTokenSource = new();
|
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
Progess = 0;
|
Progess = 0;
|
||||||
|
|
@ -104,7 +104,7 @@ public class UnpackClient(Download download, String destinationPath)
|
||||||
await FileHelper.Delete(filePath);
|
await FileHelper.Delete(filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_torrent.ClientKind == Data.Enums.Provider.TorBox)
|
if (_torrent.ClientKind == Provider.TorBox)
|
||||||
{
|
{
|
||||||
TorBoxTorrentClient.MoveHashDirContents(extractPath, _torrent);
|
TorBoxTorrentClient.MoveHashDirContents(extractPath, _torrent);
|
||||||
}
|
}
|
||||||
|
|
@ -119,7 +119,6 @@ public class UnpackClient(Download download, String destinationPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static async Task<IList<String>> GetArchiveFiles(String filePath)
|
private static async Task<IList<String>> GetArchiveFiles(String filePath)
|
||||||
{
|
{
|
||||||
await using Stream stream = File.OpenRead(filePath);
|
await using Stream stream = File.OpenRead(filePath);
|
||||||
|
|
@ -127,6 +126,7 @@ public class UnpackClient(Download download, String destinationPath)
|
||||||
var extension = Path.GetExtension(filePath);
|
var extension = Path.GetExtension(filePath);
|
||||||
|
|
||||||
IArchive archive;
|
IArchive archive;
|
||||||
|
|
||||||
if (extension == ".zip")
|
if (extension == ".zip")
|
||||||
{
|
{
|
||||||
archive = ZipArchive.OpenArchive(stream);
|
archive = ZipArchive.OpenArchive(stream);
|
||||||
|
|
@ -155,6 +155,7 @@ public class UnpackClient(Download download, String destinationPath)
|
||||||
var extension = Path.GetExtension(filePath);
|
var extension = Path.GetExtension(filePath);
|
||||||
|
|
||||||
IArchive archive;
|
IArchive archive;
|
||||||
|
|
||||||
if (extension == ".zip")
|
if (extension == ".zip")
|
||||||
{
|
{
|
||||||
archive = ZipArchive.OpenArchive(fi);
|
archive = ZipArchive.OpenArchive(fi);
|
||||||
|
|
@ -174,4 +175,4 @@ public class UnpackClient(Download download, String destinationPath)
|
||||||
|
|
||||||
GC.Collect();
|
GC.Collect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,10 @@ namespace RdtClient.Service.Wrappers;
|
||||||
|
|
||||||
public interface IProcess : IDisposable
|
public interface IProcess : IDisposable
|
||||||
{
|
{
|
||||||
|
public ProcessStartInfo StartInfo { get; set; }
|
||||||
event EventHandler<String?>? OutputDataReceived;
|
event EventHandler<String?>? OutputDataReceived;
|
||||||
event EventHandler<String?>? ErrorDataReceived;
|
event EventHandler<String?>? ErrorDataReceived;
|
||||||
|
|
||||||
public ProcessStartInfo StartInfo { get; set; }
|
|
||||||
|
|
||||||
void BeginOutputReadLine();
|
void BeginOutputReadLine();
|
||||||
void BeginErrorReadLine();
|
void BeginErrorReadLine();
|
||||||
Boolean WaitForExit(Int32 milliseconds);
|
Boolean WaitForExit(Int32 milliseconds);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
namespace RdtClient.Service.Wrappers;
|
namespace RdtClient.Service.Wrappers;
|
||||||
|
|
||||||
public class ProcessFactory: IProcessFactory
|
public class ProcessFactory : IProcessFactory
|
||||||
{
|
{
|
||||||
public IProcess NewProcess()
|
public IProcess NewProcess()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,10 @@ public class AuthController(Authentication authentication, Settings settings) :
|
||||||
{
|
{
|
||||||
return StatusCode(402, "Setup required");
|
return StatusCode(402, "Setup required");
|
||||||
}
|
}
|
||||||
|
|
||||||
return StatusCode(403);
|
return StatusCode(403);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -49,7 +49,7 @@ public class AuthController(Authentication authentication, Settings settings) :
|
||||||
{
|
{
|
||||||
return StatusCode(401);
|
return StatusCode(401);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (String.IsNullOrEmpty(request.UserName) || String.IsNullOrEmpty(request.Password))
|
if (String.IsNullOrEmpty(request.UserName) || String.IsNullOrEmpty(request.Password))
|
||||||
{
|
{
|
||||||
return BadRequest("Invalid UserName or Password");
|
return BadRequest("Invalid UserName or Password");
|
||||||
|
|
@ -61,7 +61,7 @@ public class AuthController(Authentication authentication, Settings settings) :
|
||||||
{
|
{
|
||||||
return BadRequest(registerResult.Errors.First().Description);
|
return BadRequest(registerResult.Errors.First().Description);
|
||||||
}
|
}
|
||||||
|
|
||||||
await authentication.Login(request.UserName, request.Password);
|
await authentication.Login(request.UserName, request.Password);
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
|
|
@ -119,15 +119,16 @@ public class AuthController(Authentication authentication, Settings settings) :
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("Logout")]
|
[Route("Logout")]
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<ActionResult> Logout()
|
public async Task<ActionResult> Logout()
|
||||||
{
|
{
|
||||||
await authentication.Logout();
|
await authentication.Logout();
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("Update")]
|
[Route("Update")]
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Authorize(Policy = "AuthSetting")]
|
[Authorize(Policy = "AuthSetting")]
|
||||||
|
|
@ -170,4 +171,4 @@ public class AuthControllerUpdateRequest
|
||||||
{
|
{
|
||||||
public String? UserName { get; set; }
|
public String? UserName { get; set; }
|
||||||
public String? Password { get; set; }
|
public String? Password { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,13 @@ using Microsoft.AspNetCore.Mvc;
|
||||||
using RdtClient.Data.Enums;
|
using RdtClient.Data.Enums;
|
||||||
using RdtClient.Data.Models.QBittorrent;
|
using RdtClient.Data.Models.QBittorrent;
|
||||||
using RdtClient.Service.Services;
|
using RdtClient.Service.Services;
|
||||||
|
using RealDebridException = RDNET.RealDebridException;
|
||||||
|
|
||||||
namespace RdtClient.Web.Controllers;
|
namespace RdtClient.Web.Controllers;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This API behaves as a regular QBittorrent 4+ API
|
/// This API behaves as a regular QBittorrent 4+ API
|
||||||
/// Documentation is found here: https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)
|
/// Documentation is found here: https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-(qBittorrent-4.1)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/v2")]
|
[Route("api/v2")]
|
||||||
|
|
@ -41,7 +41,7 @@ public class QBittorrentController(ILogger<QBittorrentController> logger, QBitto
|
||||||
|
|
||||||
return Ok("Fails.");
|
return Ok("Fails.");
|
||||||
}
|
}
|
||||||
|
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
[Route("auth/login")]
|
[Route("auth/login")]
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
|
|
@ -59,6 +59,7 @@ public class QBittorrentController(ILogger<QBittorrentController> logger, QBitto
|
||||||
logger.LogDebug($"Auth logout");
|
logger.LogDebug($"Auth logout");
|
||||||
|
|
||||||
await qBittorrent.AuthLogout();
|
await qBittorrent.AuthLogout();
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -92,6 +93,7 @@ public class QBittorrentController(ILogger<QBittorrentController> logger, QBitto
|
||||||
Qt = "5.15.2",
|
Qt = "5.15.2",
|
||||||
Zlib = "1.2.11"
|
Zlib = "1.2.11"
|
||||||
};
|
};
|
||||||
|
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,6 +113,7 @@ public class QBittorrentController(ILogger<QBittorrentController> logger, QBitto
|
||||||
public async Task<ActionResult<AppPreferences>> AppPreferences()
|
public async Task<ActionResult<AppPreferences>> AppPreferences()
|
||||||
{
|
{
|
||||||
var result = await qBittorrent.AppPreferences();
|
var result = await qBittorrent.AppPreferences();
|
||||||
|
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -130,9 +133,10 @@ public class QBittorrentController(ILogger<QBittorrentController> logger, QBitto
|
||||||
public ActionResult<AppPreferences> AppDefaultSavePath()
|
public ActionResult<AppPreferences> AppDefaultSavePath()
|
||||||
{
|
{
|
||||||
var result = Settings.AppDefaultSavePath;
|
var result = Settings.AppDefaultSavePath;
|
||||||
|
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = "AuthSetting")]
|
[Authorize(Policy = "AuthSetting")]
|
||||||
[Route("torrents/info")]
|
[Route("torrents/info")]
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
|
|
@ -339,7 +343,7 @@ public class QBittorrentController(ILogger<QBittorrentController> logger, QBitto
|
||||||
return BadRequest($"Invalid torrent link format {url}");
|
return BadRequest($"Invalid torrent link format {url}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (RDNET.RealDebridException ex)
|
catch (RealDebridException ex)
|
||||||
{
|
{
|
||||||
// Infringing file.
|
// Infringing file.
|
||||||
if (ex.ErrorCode == 35)
|
if (ex.ErrorCode == 35)
|
||||||
|
|
@ -369,7 +373,7 @@ public class QBittorrentController(ILogger<QBittorrentController> logger, QBitto
|
||||||
await qBittorrent.TorrentsAddFile(fileBytes, request.Category, request.Priority);
|
await qBittorrent.TorrentsAddFile(fileBytes, request.Category, request.Priority);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.Urls != null)
|
if (request.Urls != null)
|
||||||
{
|
{
|
||||||
return await TorrentsAdd(request);
|
return await TorrentsAdd(request);
|
||||||
|
|
@ -386,7 +390,7 @@ public class QBittorrentController(ILogger<QBittorrentController> logger, QBitto
|
||||||
{
|
{
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = "AuthSetting")]
|
[Authorize(Policy = "AuthSetting")]
|
||||||
[Route("torrents/setCategory")]
|
[Route("torrents/setCategory")]
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
|
|
@ -450,7 +454,7 @@ public class QBittorrentController(ILogger<QBittorrentController> logger, QBitto
|
||||||
{
|
{
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = "AuthSetting")]
|
[Authorize(Policy = "AuthSetting")]
|
||||||
[Route("torrents/removeCategories")]
|
[Route("torrents/removeCategories")]
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
|
|
@ -480,7 +484,7 @@ public class QBittorrentController(ILogger<QBittorrentController> logger, QBitto
|
||||||
{
|
{
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = "AuthSetting")]
|
[Authorize(Policy = "AuthSetting")]
|
||||||
[Route("torrents/tags")]
|
[Route("torrents/tags")]
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
|
|
@ -535,7 +539,7 @@ public class QBittorrentController(ILogger<QBittorrentController> logger, QBitto
|
||||||
{
|
{
|
||||||
return await SyncMainData();
|
return await SyncMainData();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize(Policy = "AuthSetting")]
|
[Authorize(Policy = "AuthSetting")]
|
||||||
[Route("transfer/info")]
|
[Route("transfer/info")]
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
|
|
@ -563,7 +567,7 @@ public class QBTorrentsInfoRequest
|
||||||
{
|
{
|
||||||
public String? Category { get; set; }
|
public String? Category { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class QBTorrentsHashRequest
|
public class QBTorrentsHashRequest
|
||||||
{
|
{
|
||||||
public String? Hash { get; set; }
|
public String? Hash { get; set; }
|
||||||
|
|
@ -587,7 +591,7 @@ public class QBTorrentsSetCategoryRequest
|
||||||
public String? Hashes { get; set; }
|
public String? Hashes { get; set; }
|
||||||
public String? Category { get; set; }
|
public String? Category { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class QBTorrentsCreateCategoryRequest
|
public class QBTorrentsCreateCategoryRequest
|
||||||
{
|
{
|
||||||
public String? Category { get; set; }
|
public String? Category { get; set; }
|
||||||
|
|
@ -601,4 +605,4 @@ public class QBTorrentsRemoveCategoryRequest
|
||||||
public class QBTorrentsHashesRequest
|
public class QBTorrentsHashesRequest
|
||||||
{
|
{
|
||||||
public String? Hashes { get; set; }
|
public String? Hashes { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ using RdtClient.Data.Models.Internal;
|
||||||
using RdtClient.Service.Helpers;
|
using RdtClient.Service.Helpers;
|
||||||
using RdtClient.Service.Services;
|
using RdtClient.Service.Services;
|
||||||
using RdtClient.Service.Services.Downloaders;
|
using RdtClient.Service.Services.Downloaders;
|
||||||
|
using DownloadClient = RdtClient.Data.Enums.DownloadClient;
|
||||||
|
|
||||||
namespace RdtClient.Web.Controllers;
|
namespace RdtClient.Web.Controllers;
|
||||||
|
|
||||||
|
|
@ -21,6 +22,7 @@ public class SettingsController(Settings settings, Torrents torrents) : Controll
|
||||||
public ActionResult Get()
|
public ActionResult Get()
|
||||||
{
|
{
|
||||||
var result = SettingData.GetAll();
|
var result = SettingData.GetAll();
|
||||||
|
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -34,7 +36,7 @@ public class SettingsController(Settings settings, Torrents torrents) : Controll
|
||||||
}
|
}
|
||||||
|
|
||||||
await settings.Update(settings1);
|
await settings.Update(settings1);
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,6 +45,7 @@ public class SettingsController(Settings settings, Torrents torrents) : Controll
|
||||||
public async Task<ActionResult<Profile>> Profile()
|
public async Task<ActionResult<Profile>> Profile()
|
||||||
{
|
{
|
||||||
var profile = await torrents.GetProfile();
|
var profile = await torrents.GetProfile();
|
||||||
|
|
||||||
return Ok(profile);
|
return Ok(profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,7 +60,7 @@ public class SettingsController(Settings settings, Torrents torrents) : Controll
|
||||||
Version = version
|
Version = version
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("TestPath")]
|
[Route("TestPath")]
|
||||||
public async Task<ActionResult> TestPath([FromBody] SettingsControllerTestPathRequest? request)
|
public async Task<ActionResult> TestPath([FromBody] SettingsControllerTestPathRequest? request)
|
||||||
|
|
@ -82,12 +85,12 @@ public class SettingsController(Settings settings, Torrents torrents) : Controll
|
||||||
var testFile = $"{path}/test.txt";
|
var testFile = $"{path}/test.txt";
|
||||||
|
|
||||||
await System.IO.File.WriteAllTextAsync(testFile, "RealDebridClient Test File, you can remove this file.");
|
await System.IO.File.WriteAllTextAsync(testFile, "RealDebridClient Test File, you can remove this file.");
|
||||||
|
|
||||||
await FileHelper.Delete(testFile);
|
await FileHelper.Delete(testFile);
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("TestDownloadSpeed")]
|
[Route("TestDownloadSpeed")]
|
||||||
public async Task<ActionResult> TestDownloadSpeed(CancellationToken cancellationToken)
|
public async Task<ActionResult> TestDownloadSpeed(CancellationToken cancellationToken)
|
||||||
|
|
@ -103,12 +106,12 @@ public class SettingsController(Settings settings, Torrents torrents) : Controll
|
||||||
Link = "https://34.download.real-debrid.com/speedtest/testDefault.rar",
|
Link = "https://34.download.real-debrid.com/speedtest/testDefault.rar",
|
||||||
Torrent = new()
|
Torrent = new()
|
||||||
{
|
{
|
||||||
DownloadClient = Settings.Get.DownloadClient.Client == Data.Enums.DownloadClient.Symlink ? Data.Enums.DownloadClient.Bezzad : Settings.Get.DownloadClient.Client,
|
DownloadClient = Settings.Get.DownloadClient.Client == DownloadClient.Symlink ? DownloadClient.Bezzad : Settings.Get.DownloadClient.Client,
|
||||||
RdName = "testDefault.rar"
|
RdName = "testDefault.rar"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var downloadClient = new DownloadClient(download, download.Torrent, downloadPath, null);
|
var downloadClient = new Service.Services.DownloadClient(download, download.Torrent, downloadPath, null);
|
||||||
|
|
||||||
await downloadClient.Start();
|
await downloadClient.Start();
|
||||||
|
|
||||||
|
|
@ -134,7 +137,7 @@ public class SettingsController(Settings settings, Torrents torrents) : Controll
|
||||||
|
|
||||||
await aria2Downloader.Update(allDownloads);
|
await aria2Downloader.Update(allDownloads);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (downloadClient.BytesDone > 1024 * 1024 * 50)
|
if (downloadClient.BytesDone > 1024 * 1024 * 50)
|
||||||
{
|
{
|
||||||
await downloadClient.Cancel();
|
await downloadClient.Cancel();
|
||||||
|
|
@ -147,7 +150,7 @@ public class SettingsController(Settings settings, Torrents torrents) : Controll
|
||||||
|
|
||||||
return Ok(downloadClient.Speed);
|
return Ok(downloadClient.Speed);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("TestWriteSpeed")]
|
[Route("TestWriteSpeed")]
|
||||||
public async Task<ActionResult> TestWriteSpeed()
|
public async Task<ActionResult> TestWriteSpeed()
|
||||||
|
|
@ -176,15 +179,15 @@ public class SettingsController(Settings settings, Torrents torrents) : Controll
|
||||||
|
|
||||||
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 Ok(writeSpeed);
|
return Ok(writeSpeed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -219,4 +222,4 @@ public class SettingsControllerTestAria2cConnectionRequest
|
||||||
{
|
{
|
||||||
public String? Url { get; set; }
|
public String? Url { get; set; }
|
||||||
public String? Secret { get; set; }
|
public String? Secret { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ public class TorrentsController(ILogger<TorrentsController> logger, Torrents tor
|
||||||
public ActionResult<DiskSpaceStatus?> GetDiskSpaceStatus()
|
public ActionResult<DiskSpaceStatus?> GetDiskSpaceStatus()
|
||||||
{
|
{
|
||||||
var status = DiskSpaceMonitor.GetCurrentStatus();
|
var status = DiskSpaceMonitor.GetCurrentStatus();
|
||||||
|
|
||||||
return Ok(status);
|
return Ok(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,7 +108,7 @@ public class TorrentsController(ILogger<TorrentsController> logger, Torrents tor
|
||||||
{
|
{
|
||||||
return BadRequest();
|
return BadRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (String.IsNullOrEmpty(request.MagnetLink))
|
if (String.IsNullOrEmpty(request.MagnetLink))
|
||||||
{
|
{
|
||||||
return BadRequest("Invalid magnet link");
|
return BadRequest("Invalid magnet link");
|
||||||
|
|
@ -208,7 +209,7 @@ public class TorrentsController(ILogger<TorrentsController> logger, Torrents tor
|
||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPut]
|
[HttpPut]
|
||||||
[Route("Update")]
|
[Route("Update")]
|
||||||
public async Task<ActionResult> Update([FromBody] Torrent? torrent)
|
public async Task<ActionResult> Update([FromBody] Torrent? torrent)
|
||||||
|
|
@ -280,7 +281,7 @@ public class TorrentsController(ILogger<TorrentsController> logger, Torrents tor
|
||||||
includeError = ex.Message;
|
includeError = ex.Message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (!String.IsNullOrWhiteSpace(request.ExcludeRegex))
|
else if (!String.IsNullOrWhiteSpace(request.ExcludeRegex))
|
||||||
{
|
{
|
||||||
foreach (var availableFile in availableFiles)
|
foreach (var availableFile in availableFiles)
|
||||||
|
|
@ -339,5 +340,5 @@ public class TorrentControllerVerifyRegexRequest
|
||||||
{
|
{
|
||||||
public String? IncludeRegex { get; set; }
|
public String? IncludeRegex { get; set; }
|
||||||
public String? ExcludeRegex { get; set; }
|
public String? ExcludeRegex { get; set; }
|
||||||
public String? MagnetLink { get; set;}
|
public String? MagnetLink { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,9 @@ using RdtClient.Service;
|
||||||
using RdtClient.Service.Middleware;
|
using RdtClient.Service.Middleware;
|
||||||
using RdtClient.Service.Services;
|
using RdtClient.Service.Services;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
using Serilog.Debugging;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
using DiConfig = RdtClient.Data.DiConfig;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
|
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
|
||||||
{
|
{
|
||||||
|
|
@ -51,7 +53,7 @@ if (appSettings.Logging?.File?.Path != null)
|
||||||
.MinimumLevel.Override("System.Net.Http", LogEventLevel.Warning));
|
.MinimumLevel.Override("System.Net.Http", LogEventLevel.Warning));
|
||||||
}
|
}
|
||||||
|
|
||||||
Serilog.Debugging.SelfLog.Enable(msg =>
|
SelfLog.Enable(msg =>
|
||||||
{
|
{
|
||||||
Debug.Print(msg);
|
Debug.Print(msg);
|
||||||
Debugger.Break();
|
Debugger.Break();
|
||||||
|
|
@ -69,12 +71,12 @@ builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationSc
|
||||||
options.SlidingExpiration = true;
|
options.SlidingExpiration = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
builder.Services.AddAuthorizationBuilder()
|
||||||
builder.Services.AddAuthorizationBuilder().AddPolicy("AuthSetting", policyCorrectUser =>
|
.AddPolicy("AuthSetting",
|
||||||
{
|
policyCorrectUser =>
|
||||||
policyCorrectUser.Requirements.Add(new AuthSettingRequirement());
|
{
|
||||||
});
|
policyCorrectUser.Requirements.Add(new AuthSettingRequirement());
|
||||||
|
});
|
||||||
|
|
||||||
builder.Services.AddIdentity<IdentityUser, IdentityRole>(options =>
|
builder.Services.AddIdentity<IdentityUser, IdentityRole>(options =>
|
||||||
{
|
{
|
||||||
|
|
@ -93,8 +95,10 @@ builder.Services.ConfigureApplicationCookie(options =>
|
||||||
options.Events.OnRedirectToLogin = context =>
|
options.Events.OnRedirectToLogin = context =>
|
||||||
{
|
{
|
||||||
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
|
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
};
|
};
|
||||||
|
|
||||||
options.Cookie.Name = "SID";
|
options.Cookie.Name = "SID";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -132,7 +136,7 @@ builder.Services.AddSignalR(hubOptions =>
|
||||||
|
|
||||||
builder.Host.UseWindowsService();
|
builder.Host.UseWindowsService();
|
||||||
|
|
||||||
RdtClient.Data.DiConfig.Config(builder.Services, appSettings);
|
DiConfig.Config(builder.Services, appSettings);
|
||||||
builder.Services.RegisterRdtServices();
|
builder.Services.RegisterRdtServices();
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|
@ -160,7 +164,8 @@ try
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var basePath = !String.IsNullOrWhiteSpace(appSettings.BasePath) ? appSettings.BasePath : !String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("BASE_PATH")) ? Environment.GetEnvironmentVariable("BASE_PATH") : null;
|
var basePath = !String.IsNullOrWhiteSpace(appSettings.BasePath) ? appSettings.BasePath :
|
||||||
|
!String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("BASE_PATH")) ? Environment.GetEnvironmentVariable("BASE_PATH") : null;
|
||||||
|
|
||||||
if (basePath != null)
|
if (basePath != null)
|
||||||
{
|
{
|
||||||
|
|
@ -180,16 +185,18 @@ try
|
||||||
|
|
||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
|
|
||||||
app.UseWhen(x => !x.Request.Path.StartsWithSegments("/api"), routeBuilder =>
|
app.UseWhen(x => !x.Request.Path.StartsWithSegments("/api"),
|
||||||
{
|
routeBuilder =>
|
||||||
routeBuilder.UseSpaStaticFiles();
|
{
|
||||||
routeBuilder.UseSpa(spa =>
|
routeBuilder.UseSpaStaticFiles();
|
||||||
{
|
|
||||||
spa.Options.SourcePath = "wwwroot";
|
routeBuilder.UseSpa(spa =>
|
||||||
spa.Options.DefaultPage = "/index.html";
|
{
|
||||||
});
|
spa.Options.SourcePath = "wwwroot";
|
||||||
});
|
spa.Options.DefaultPage = "/index.html";
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Run the app
|
// Run the app
|
||||||
app.Run();
|
app.Run();
|
||||||
}
|
}
|
||||||
|
|
@ -200,4 +207,4 @@ catch (Exception ex)
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
Log.CloseAndFlush();
|
Log.CloseAndFlush();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<UserSecretsId>94c24cba-f03f-4453-a671-3640b517c573</UserSecretsId>
|
<UserSecretsId>94c24cba-f03f-4453-a671-3640b517c573</UserSecretsId>
|
||||||
<Version>1.0.0</Version>
|
<Version>1.0.0</Version>
|
||||||
<AssemblyVersion>1.0.0</AssemblyVersion>
|
<AssemblyVersion>1.0.0</AssemblyVersion>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
|
|
@ -38,7 +38,6 @@
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="10.0.3" />
|
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="10.0.3" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Identity.Core" Version="10.0.3" />
|
|
||||||
<PackageReference Include="Serilog" Version="4.3.1" />
|
<PackageReference Include="Serilog" Version="4.3.1" />
|
||||||
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
|
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||||
|
|
@ -54,4 +53,4 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
Loading…
Reference in a new issue