handle rate limiting and database errors by delaying downloads

This commit is contained in:
Sam Heinz 2026-05-26 20:16:07 +10:00
parent 157fc8b2f2
commit a86056a3ef
6 changed files with 59 additions and 15 deletions

1
.gitignore vendored
View file

@ -8,3 +8,4 @@ server/RdtClient.Web/appsettings.Development.json
data data
Dockerfile.dev Dockerfile.dev
test.bat test.bat
.DS_Store

View file

@ -273,7 +273,7 @@ public class DownloadData(DataContext dataContext, ILogger<DownloadData>? logger
await dataContext.SaveChangesAsync(); await dataContext.SaveChangesAsync();
} }
public async Task Reset(Guid downloadId) public async Task Reset(Guid downloadId, DateTimeOffset? downloadQueued = null)
{ {
var dbDownload = await dataContext.Downloads var dbDownload = await dataContext.Downloads
.FirstOrDefaultAsync(m => m.DownloadId == downloadId) .FirstOrDefaultAsync(m => m.DownloadId == downloadId)
@ -282,7 +282,7 @@ public class DownloadData(DataContext dataContext, ILogger<DownloadData>? logger
dbDownload.RetryCount = 0; dbDownload.RetryCount = 0;
dbDownload.Link = null; dbDownload.Link = null;
dbDownload.Added = DateTimeOffset.UtcNow; dbDownload.Added = DateTimeOffset.UtcNow;
dbDownload.DownloadQueued = DateTimeOffset.UtcNow; dbDownload.DownloadQueued = downloadQueued ?? DateTimeOffset.UtcNow;
dbDownload.DownloadStarted = null; dbDownload.DownloadStarted = null;
dbDownload.DownloadFinished = null; dbDownload.DownloadFinished = null;
dbDownload.UnpackingQueued = null; dbDownload.UnpackingQueued = null;

View file

@ -69,7 +69,10 @@ public class TorBoxDebridClient(ILogger<TorBoxDebridClient> logger, IHttpClientF
return await HandleAddTorrentErrors(async asQueued => return await HandleAddTorrentErrors(async asQueued =>
{ {
var user = await GetClient().User.GetAsync(true); var user = await GetClient().User.GetAsync(true);
var result = await GetClient(DiConfig.TORBOX_CLIENT_SLOW).Torrents.AddMagnetAsync(magnetLink, user.Data?.Settings?.SeedTorrents ?? 3, as_queued: asQueued); var result = await GetClient(DiConfig.TORBOX_CLIENT_SLOW).Torrents.AddMagnetAsync(magnetLink,
user.Data?.Settings?.SeedTorrents ?? 3,
allowZip: Settings.Get.Provider.PreferZippedDownloads,
as_queued: asQueued);
return result.Data!.Hash!; return result.Data!.Hash!;
}); });
@ -80,7 +83,10 @@ public class TorBoxDebridClient(ILogger<TorBoxDebridClient> logger, IHttpClientF
return await HandleAddTorrentErrors(async asQueued => return await HandleAddTorrentErrors(async asQueued =>
{ {
var user = await GetClient().User.GetAsync(true); var user = await GetClient().User.GetAsync(true);
var result = await GetClient(DiConfig.TORBOX_CLIENT_SLOW).Torrents.AddFileAsync(bytes, user.Data?.Settings?.SeedTorrents ?? 3, as_queued: asQueued); var result = await GetClient(DiConfig.TORBOX_CLIENT_SLOW).Torrents.AddFileAsync(bytes,
user.Data?.Settings?.SeedTorrents ?? 3,
allowZip: Settings.Get.Provider.PreferZippedDownloads,
as_queued: asQueued);
return result.Data!.Hash!; return result.Data!.Hash!;
}); });
@ -372,7 +378,7 @@ public class TorBoxDebridClient(ILogger<TorBoxDebridClient> logger, IHttpClientF
} }
var httpClient = httpClientFactory.CreateClient(clientId); var httpClient = httpClientFactory.CreateClient(clientId);
var torBoxNetClient = new TorBoxNetClient(null, httpClient); var torBoxNetClient = new TorBoxNetClient(null, httpClient, retryCount: 5);
torBoxNetClient.UseApiAuthentication(apiKey); torBoxNetClient.UseApiAuthentication(apiKey);
// Get the server time to fix up the timezones on results // Get the server time to fix up the timezones on results
@ -513,7 +519,7 @@ public class TorBoxDebridClient(ILogger<TorBoxDebridClient> logger, IHttpClientF
{ {
throw rateLimitException; throw rateLimitException;
} }
catch (TorBoxException ex) when ("active_limit".Equals(ex.Error, StringComparison.OrdinalIgnoreCase)) catch (TorBoxException ex) when (IsRateLimit(ex))
{ {
coordinator.UpdateCooldown(TorBoxApiHost, TimeSpan.FromMinutes(2)); coordinator.UpdateCooldown(TorBoxApiHost, TimeSpan.FromMinutes(2));
@ -541,7 +547,7 @@ public class TorBoxDebridClient(ILogger<TorBoxDebridClient> logger, IHttpClientF
{ {
throw rateLimitException; throw rateLimitException;
} }
catch (TorBoxException ex) when ("active_limit".Equals(ex.Error, StringComparison.OrdinalIgnoreCase)) catch (TorBoxException ex) when (IsRateLimit(ex))
{ {
coordinator.UpdateCooldown(TorBoxApiHost, TimeSpan.FromMinutes(2)); coordinator.UpdateCooldown(TorBoxApiHost, TimeSpan.FromMinutes(2));
@ -560,6 +566,12 @@ public class TorBoxDebridClient(ILogger<TorBoxDebridClient> logger, IHttpClientF
return await HandleErrors(() => action(false)); return await HandleErrors(() => action(false));
} }
private static Boolean IsRateLimit(TorBoxException exception)
{
return exception.Error.Equals("RATE_LIMIT", StringComparison.OrdinalIgnoreCase)
|| exception.Error.Equals("ACTIVE_LIMIT", StringComparison.OrdinalIgnoreCase);
}
private async Task<String> HandleAddUsenetErrors(Func<Boolean, Task<String>> action) private async Task<String> HandleAddUsenetErrors(Func<Boolean, Task<String>> action)
{ {
return await HandleErrors(() => action(false)); return await HandleErrors(() => action(false));

View file

@ -86,8 +86,8 @@ public class Downloads(DownloadData downloadData) : IDownloads
await downloadData.DeleteForTorrent(torrentId); await downloadData.DeleteForTorrent(torrentId);
} }
public async Task Reset(Guid downloadId) public async Task Reset(Guid downloadId, DateTimeOffset? downloadQueued = null)
{ {
await downloadData.Reset(downloadId); await downloadData.Reset(downloadId, downloadQueued);
} }
} }

View file

@ -21,5 +21,5 @@ public interface IDownloads
Task UpdateRetryCount(Guid downloadId, Int32 retryCount); Task UpdateRetryCount(Guid downloadId, Int32 retryCount);
Task UpdateRemoteId(Guid downloadId, String remoteId); Task UpdateRemoteId(Guid downloadId, String remoteId);
Task DeleteForTorrent(Guid torrentId); Task DeleteForTorrent(Guid torrentId);
Task Reset(Guid downloadId); Task Reset(Guid downloadId, DateTimeOffset? downloadQueued = null);
} }

View file

@ -477,7 +477,11 @@ public class TorrentRunner(
{ {
// Check if there are any downloads that are queued and can be started. // Check if there are any downloads that are queued and can be started.
var queuedDownloads = torrent.Downloads var queuedDownloads = torrent.Downloads
.Where(m => m.Completed == null && m.DownloadQueued != null && m.DownloadStarted == null && m.Error == null) .Where(m => m.Completed == null
&& m.DownloadQueued != null
&& m.DownloadQueued <= DateTimeOffset.UtcNow
&& m.DownloadStarted == null
&& m.Error == null)
.OrderBy(m => m.DownloadQueued) .OrderBy(m => m.DownloadQueued)
.ToList(); .ToList();
@ -528,10 +532,24 @@ public class TorrentRunner(
{ {
logger.LogError(ex, "Cannot unrestrict link: {ex.Message}", ex.Message); logger.LogError(ex, "Cannot unrestrict link: {ex.Message}", ex.Message);
if (download.RetryCount < torrent.DownloadRetryAttempts)
{
var retryCount = download.RetryCount + 1;
var retryDelay = GetDownloadLinkRetryDelay(retryCount);
var retryAt = DateTimeOffset.UtcNow.Add(retryDelay);
Log($"Retrying download link generation {retryCount}/{torrent.DownloadRetryAttempts} at {retryAt:u}", download, torrent);
await downloads.Reset(download.DownloadId, retryAt);
await downloads.UpdateRetryCount(download.DownloadId, retryCount);
}
else
{
await downloads.UpdateError(download.DownloadId, ex.Message); await downloads.UpdateError(download.DownloadId, ex.Message);
await downloads.UpdateCompleted(download.DownloadId, DateTimeOffset.UtcNow); await downloads.UpdateCompleted(download.DownloadId, DateTimeOffset.UtcNow);
download.Error = ex.Message; download.Error = ex.Message;
download.Completed = DateTimeOffset.UtcNow; download.Completed = DateTimeOffset.UtcNow;
}
return; return;
} }
@ -782,6 +800,19 @@ public class TorrentRunner(
}); });
} }
private static TimeSpan GetDownloadLinkRetryDelay(Int32 retryCount)
{
var seconds = retryCount switch
{
<= 1 => 15,
2 => 30,
3 => 60,
_ => 120
};
return TimeSpan.FromSeconds(seconds);
}
private void Log(String message, Download? download, Torrent? torrent) private void Log(String message, Download? download, Torrent? torrent)
{ {
if (download != null) if (download != null)