Skip to content

Commit a2129ee

Browse files
authored
Merge pull request #99 from mateof/feat/configurable-parallel-transfers
feat: configurable parallel chunk transfers for faster downloads/uploads
2 parents e4e29dc + 893269c commit a2129ee

3 files changed

Lines changed: 92 additions & 0 deletions

File tree

TelegramDownloader/Data/TelegramService.cs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ private void createDownloadFolder()
161161
private void newClient()
162162
{
163163
client = new WTelegram.Client(Convert.ToInt32(GeneralConfigStatic.tlconfig?.api_id ?? Environment.GetEnvironmentVariable("api_id")), GeneralConfigStatic.tlconfig?.hash_id ?? Environment.GetEnvironmentVariable("hash_id"), UserService.USERDATAFOLDER + "/WTelegram.session");
164+
ApplyConfiguredParallelTransfers(client);
164165
if (GeneralConfigStatic.config.ShouldShowLogInTerminal)
165166
{
166167
// WTelegram.Helpers.Log = (lvl, str) => Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} [{"TDIWE!"[lvl]}] {str}");
@@ -171,6 +172,67 @@ private void newClient()
171172

172173
}
173174

175+
// WTelegramClient requests file parts through a per-client semaphore that
176+
// defaults to 2 parts in flight, capping transfers at ~1MB per round-trip.
177+
// Tracks the value applied to each client instance (main client and the
178+
// media-DC clients WTelegram creates internally, which do NOT inherit the
179+
// main client's ParallelTransfers).
180+
private static readonly ConditionalWeakTable<WTelegram.Client, StrongBox<int>> appliedParallelTransfers = new();
181+
private const int WTELEGRAM_DEFAULT_PARALLEL_TRANSFERS = 2;
182+
183+
public static int GetConfiguredParallelTransfers()
184+
{
185+
return Math.Clamp(GeneralConfigStatic.config?.ParallelTransfers ?? 4, 1, 16);
186+
}
187+
188+
private static void ApplyConfiguredParallelTransfers(WTelegram.Client c)
189+
{
190+
if (c == null)
191+
return;
192+
int desired = GetConfiguredParallelTransfers();
193+
int delta;
194+
lock (appliedParallelTransfers)
195+
{
196+
StrongBox<int> applied = appliedParallelTransfers.GetOrCreateValue(c);
197+
if (applied.Value == 0)
198+
applied.Value = WTELEGRAM_DEFAULT_PARALLEL_TRANSFERS;
199+
delta = desired - applied.Value;
200+
if (delta == 0)
201+
return;
202+
applied.Value = desired;
203+
}
204+
try
205+
{
206+
// The ParallelTransfers setter adjusts the semaphore relative to its
207+
// CURRENT count, which is lower while parts are in flight. Applying
208+
// our delta on top of the current value keeps the configured maximum
209+
// correct even if a transfer is running on this client.
210+
c.ParallelTransfers = c.ParallelTransfers + delta;
211+
}
212+
catch (Exception)
213+
{
214+
// Never let a tuning failure break a transfer.
215+
}
216+
}
217+
218+
/// <summary>
219+
/// Resolves the client instance that WTelegram's DownloadFileAsync will use
220+
/// for the given file DC (dc_id == 0 means the main client) and applies the
221+
/// configured chunk parallelism to it before the transfer starts.
222+
/// </summary>
223+
private async Task PrepareTransferClientAsync(int dcId)
224+
{
225+
try
226+
{
227+
WTelegram.Client c = dcId == 0 ? client : await client.GetClientForDC(-dcId, true);
228+
ApplyConfiguredParallelTransfers(c);
229+
}
230+
catch (Exception ex)
231+
{
232+
_logger.LogDebug(ex, "Could not prepare transfer client for DC {DcId}", dcId);
233+
}
234+
}
235+
174236
public async Task<User> CallQrGenerator(Action<string> func, CancellationToken ct, bool logoutFirst = false)
175237
{
176238
return await client.LoginWithQRCode(func, logoutFirst: logoutFirst, ct: ct);
@@ -701,6 +763,7 @@ public async Task<Message> uploadFile(string chatId, Stream file, string fileNam
701763

702764
try
703765
{
766+
ApplyConfiguredParallelTransfers(client);
704767
var inputFile = await client.UploadFileAsync(file, fileName, um.ProgressCallback);
705768
var result = await client.SendMediaAsync(peer, caption ?? fileName, inputFile, mimeType);
706769
_logger.LogInformation("File upload completed - FileName: {FileName}, MessageId: {MessageId}", fileName, result.id);
@@ -1205,6 +1268,7 @@ public async Task<Stream> DownloadFileAndReturn(ChatMessages message, Stream ms
12051268
model.name = filename;
12061269
_logger.LogInformation("Starting document download - FileName: {FileName}, Size: {SizeMB:F2}MB", filename, document.size / (1024.0 * 1024.0));
12071270
MemoryStream dest = new MemoryStream();
1271+
await PrepareTransferClientAsync(document.dc_id);
12081272
await client.DownloadFileAsync(document, ms ?? dest, (PhotoSizeBase)null, model.ProgressCallback);
12091273
_logger.LogInformation("Document download completed - FileName: {FileName}", filename);
12101274
return ms ?? dest;
@@ -1253,6 +1317,7 @@ public async Task<Stream> DownloadFileAndReturnWithOffset(ChatMessages message,
12531317
if (offset == 0)
12541318
{
12551319
MemoryStream dest = new MemoryStream();
1320+
await PrepareTransferClientAsync(document.dc_id);
12561321
await client.DownloadFileAsync(document, ms ?? dest, (PhotoSizeBase)null, model.ProgressCallback);
12571322
_logger.LogInformation("Document download completed - FileName: {FileName}", filename);
12581323
return ms ?? dest;
@@ -1365,6 +1430,7 @@ public async Task<string> DownloadFile(ChatMessages message, string fileName = n
13651430
_tis.addToDownloadList(model);
13661431
_logger.LogInformation("Starting file download to disk - FileName: {FileName}, Size: {SizeMB:F2}MB", filename, document.size / (1024.0 * 1024.0));
13671432
using var dest = new FileStream($"{Path.Combine(folder != null ? folder : Path.Combine(Environment.CurrentDirectory, "local", "temp"), filename)}", FileMode.Create, FileAccess.Write);
1433+
await PrepareTransferClientAsync(document.dc_id);
13681434
await client.DownloadFileAsync(document, dest, (PhotoSizeBase)null, model.ProgressCallback);
13691435
_logger.LogInformation("File download to disk completed - FileName: {FileName}", filename);
13701436
}

TelegramDownloader/Models/GeneralConfig.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,17 @@ public StreamingMode GetEffectiveStreamingMode()
178178
/// </summary>
179179
public int MemorySplitSizeGB { get; set; } = 2;
180180

181+
// Transfer Speed Settings
182+
/// <summary>
183+
/// Number of 512KB file chunks requested in parallel per transfer (1-16).
184+
/// WTelegramClient's default of 2 caps throughput at roughly 1MB per
185+
/// round-trip to Telegram's data center (~5-7 MB/s on typical latency).
186+
/// Higher values remove that latency bottleneck; the server-side speed
187+
/// limit for non-Premium accounts still applies. Takes effect on the
188+
/// next transfer, no restart needed.
189+
/// </summary>
190+
public int ParallelTransfers { get; set; } = 4;
191+
181192
}
182193

183194
public class TLConfig

TelegramDownloader/Pages/Config.razor

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,21 @@
230230
</div>
231231
</div>
232232

233+
<div class="config-item">
234+
<div class="config-item-info">
235+
<div class="config-item-label">
236+
<i class="bi bi-lightning-charge"></i>
237+
Parallel Chunk Transfers
238+
</div>
239+
<div class="config-item-description">
240+
Number of 512KB chunks requested in parallel per transfer (1-16). Higher values improve download/upload speed by removing the latency bottleneck; Premium accounts benefit the most. Applied on the next transfer.
241+
</div>
242+
</div>
243+
<div class="config-item-control">
244+
<NumberInput TValue="int" @bind-Value="Model!.ParallelTransfers" Min="1" Max="16" EnableMinMax="true" class="form-control" style="width: 80px;" />
245+
</div>
246+
</div>
247+
233248
<div class="config-item">
234249
<div class="config-item-info">
235250
<div class="config-item-label">

0 commit comments

Comments
 (0)