Skip to content

Commit e00b7ac

Browse files
authored
Merge pull request #37 from ZenonEl/feat/download-queue
feat(queue): add download queue with configurable concurrency limit
2 parents 4ef0170 + f48a15b commit e00b7ac

5 files changed

Lines changed: 80 additions & 2 deletions

File tree

Config/Config.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ class Config
4444
public static int torControlPort = 9051;
4545
public static int torChangingChainInterval = 5; // Minutes
4646

47+
// Download Queue
48+
public static int maxConcurrentDownloads = 3;
49+
4750
// Delays
4851
public static int videoGetDelay = 1000;
4952
public static int contactSendDelay = 1000;
@@ -87,6 +90,7 @@ public static void LoadConfig()
8790
dbType = configuration.GetValue("AppSettings:DatabaseType", "sqlite");
8891
isUseGalleryDl = configuration.GetValue("AppSettings:UseGalleryDl", false);
8992
accessDeniedMessageContact = configuration.GetValue("AppSettings:AccessDeniedMessageContact", " ");
93+
maxConcurrentDownloads = configuration.GetValue("AppSettings:MaxConcurrentDownloads", 3);
9094

9195
videoGetDelay = configuration.GetValue("MessageDelaySettings:VideoGetDelay", 1000);
9296
contactSendDelay = configuration.GetValue("MessageDelaySettings:ContactSendDelay", 1000);

Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ static async Task Main(string[] args)
6767
Scheduler scheduler = app.Services.GetRequiredService<Scheduler>();
6868

6969
Log.Information($"Log level: {Config.logLevel}");
70+
DownloadQueue.Initialize(Config.maxConcurrentDownloads);
7071
scheduler.Init();
7172

7273
await tgBot.Start();

TelegramBot/MediaDownloader.cs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,28 @@ private async Task UpdateHandler(ITelegramBotClient botClient, Update update, Ca
152152
public async Task HandleMediaRequest(ITelegramBotClient botClient, string contentUrl, long chatId, Message statusMessage,
153153
List<long>? targetUserIds = null, bool groupChat = false, string caption = "")
154154
{
155-
List<byte[]>? mediaFiles = await MediaGet.DownloadMedia(botClient, contentUrl, statusMessage, cancellationToken);
155+
List<byte[]>? mediaFiles = await DownloadQueue.EnqueueAsync(
156+
async () => await MediaGet.DownloadMedia(botClient, contentUrl, statusMessage, cancellationToken),
157+
position =>
158+
{
159+
_ = Task.Run(async () =>
160+
{
161+
try
162+
{
163+
await botClient.EditMessageText(
164+
statusMessage.Chat.Id,
165+
statusMessage.MessageId,
166+
$"⏳ Queued (#{position})",
167+
cancellationToken: cancellationToken);
168+
}
169+
catch (Exception ex)
170+
{
171+
Log.Debug(ex, "Error editing queue status message.");
172+
}
173+
});
174+
},
175+
cancellationToken);
176+
156177
if (mediaFiles?.Count > 0)
157178
{
158179
Log.Debug($"Downloaded {mediaFiles.Count} files");
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright (C) 2024-2025 ZenonEl
2+
// This program is free software: you can redistribute it and/or modify
3+
// it under the terms of the GNU Affero General Public License as published
4+
// by the Free Software Foundation, either version 3 of the License, or
5+
// (at your option) any later version.
6+
7+
// Эта программа является свободным программным обеспечением: вы можете распространять и/или изменять
8+
// её на условиях Стандартной общественной лицензии GNU Affero, опубликованной
9+
// Фондом свободного программного обеспечения, либо версии 3 лицензии, либо
10+
// (по вашему выбору) любой более поздней версии.
11+
12+
namespace TelegramMediaRelayBot
13+
{
14+
public static class DownloadQueue
15+
{
16+
private static SemaphoreSlim _semaphore = null!;
17+
private static int _queuedCount = 0;
18+
19+
public static void Initialize(int maxConcurrent)
20+
{
21+
_semaphore = new SemaphoreSlim(maxConcurrent, maxConcurrent);
22+
}
23+
24+
public static async Task<T?> EnqueueAsync<T>(
25+
Func<Task<T?>> downloadFunc,
26+
Action<int>? onQueued = null,
27+
CancellationToken ct = default)
28+
{
29+
int position = Interlocked.Increment(ref _queuedCount);
30+
try
31+
{
32+
if (_semaphore.CurrentCount == 0)
33+
onQueued?.Invoke(position);
34+
35+
await _semaphore.WaitAsync(ct);
36+
Interlocked.Decrement(ref _queuedCount);
37+
38+
return await downloadFunc();
39+
}
40+
finally
41+
{
42+
_semaphore.Release();
43+
}
44+
}
45+
46+
public static int QueuedCount => _queuedCount;
47+
public static int ActiveCount => Config.maxConcurrentDownloads - _semaphore.CurrentCount;
48+
}
49+
}

appsettings.json.example

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@
1414

1515
"_comment_telegram_api": "TelegramApiBaseUrl: custom Bot API server URL (null = default). TelegramApiProxy: proxy for Telegram API requests, useful in regions where Telegram is blocked.",
1616
"TelegramApiBaseUrl": null,
17-
"TelegramApiProxy": null
17+
"TelegramApiProxy": null,
18+
19+
"_comment_queue": "Maximum number of simultaneous media downloads. Prevents system overload when many links arrive at once.",
20+
"MaxConcurrentDownloads": 3
1821
},
1922
"_comment_tor": "Tor SOCKS proxy for media downloads and automatic circuit rotation.",
2023
"Tor": {

0 commit comments

Comments
 (0)