Skip to content

Commit 13bf92f

Browse files
committed
fix: show progress and honor cancel for progressive cache downloads
The background cache download appeared in the downloads list but never updated its progress and could not be stopped: - Report progress through DownloadModel.ProgressCallback (updates percentage, transmitted/size strings, global speed stats and fires the UI events) instead of writing _transmitted directly. The task is now also marked Completed when the download finishes. - Check the task state on every chunk so pressing Cancel (or Pause) in the downloads list actually stops the background download. - A user cancel is remembered for 1 hour per file so ongoing playback range requests don't immediately restart the cache download; the stream keeps working through direct Telegram fetches. A later playback caches again. - Start the progress counter at the resume offset so resumed downloads don't inflate the global speed stats. - Incomplete downloads no longer linger as 'Working' in the list.
1 parent 128776a commit 13bf92f

1 file changed

Lines changed: 62 additions & 10 deletions

File tree

TelegramDownloader/Services/ProgressiveDownloadService.cs

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ public class ProgressiveDownloadInfo
4747
public bool IsDownloading { get; set; }
4848
public DateTime StartTime { get; set; }
4949
public CancellationTokenSource? CancellationTokenSource { get; set; }
50+
/// <summary>
51+
/// Set when the user cancelled the background download from the downloads list.
52+
/// While set (and not expired), new range requests won't restart the cache download;
53+
/// playback keeps working through direct Telegram fetches.
54+
/// </summary>
55+
public bool CancelledByUser { get; set; }
56+
public DateTime CancelledAtUtc { get; set; }
5057
}
5158

5259
public class ProgressiveDownloadService : IProgressiveDownloadService
@@ -109,14 +116,23 @@ public async Task<ProgressiveDownloadInfo> StartOrGetDownloadAsync(
109116
string filePath)
110117
{
111118
// Quick check if already complete
112-
if (_activeDownloads.TryGetValue(cacheKey, out var existingInfo) && existingInfo.IsComplete)
119+
if (_activeDownloads.TryGetValue(cacheKey, out var existingInfo))
113120
{
114-
if (File.Exists(existingInfo.FilePath))
121+
if (existingInfo.IsComplete)
115122
{
123+
if (File.Exists(existingInfo.FilePath))
124+
{
125+
return existingInfo;
126+
}
127+
// Cache file was evicted: forget the stale entry and re-download
128+
_activeDownloads.TryRemove(cacheKey, out _);
129+
}
130+
else if (IsUserCancelActive(existingInfo))
131+
{
132+
// User stopped this cache download: don't restart it on every range
133+
// request; playback is served through direct Telegram fetches instead
116134
return existingInfo;
117135
}
118-
// Cache file was evicted: forget the stale entry and re-download
119-
_activeDownloads.TryRemove(cacheKey, out _);
120136
}
121137

122138
// Check if file already exists and is complete
@@ -149,6 +165,7 @@ public async Task<ProgressiveDownloadInfo> StartOrGetDownloadAsync(
149165
if (_activeDownloads.TryGetValue(cacheKey, out existingInfo))
150166
{
151167
if (existingInfo.IsDownloading ||
168+
IsUserCancelActive(existingInfo) ||
152169
(existingInfo.IsComplete && File.Exists(existingInfo.FilePath)))
153170
{
154171
return existingInfo;
@@ -229,6 +246,9 @@ private async Task DownloadInBackgroundAsync(
229246
path = info.FilePath,
230247
name = Path.GetFileName(info.FilePath),
231248
_size = info.TotalSize,
249+
// Start the counter at the resume offset so progress and global
250+
// speed stats only account for newly downloaded bytes
251+
_transmitted = startOffset,
232252
channelName = _ts.getChatName(Convert.ToInt64(channelId))
233253
};
234254
_tis.addToDownloadList(dm);
@@ -266,6 +286,16 @@ private async Task DownloadInBackgroundAsync(
266286

267287
while (localOffset < partSize && !ct.IsCancellationRequested)
268288
{
289+
// React to Cancel/Pause pressed in the downloads list
290+
if (dm.state == StateTask.Canceled || dm.state == StateTask.Paused)
291+
{
292+
_logger.LogInformation("Background download stopped by user: {CacheKey}", info.CacheKey);
293+
info.CancelledByUser = true;
294+
info.CancelledAtUtc = DateTime.UtcNow;
295+
aborted = true;
296+
break;
297+
}
298+
269299
var toDownload = (int)Math.Min(chunkSize, partSize - localOffset);
270300

271301
try
@@ -283,15 +313,22 @@ private async Task DownloadInBackgroundAsync(
283313

284314
localOffset += chunk.Length;
285315
info.DownloadedBytes = partStartGlobal + localOffset;
286-
dm._transmitted = info.DownloadedBytes;
287316
consecutiveErrors = 0;
288317

289-
// Log progress every 10%
290-
var progress = (double)info.DownloadedBytes / info.TotalSize * 100;
291-
if ((int)progress % 10 == 0)
318+
try
292319
{
293-
_logger.LogDebug("Download progress: {FileName} - {Progress:F1}%",
294-
Path.GetFileName(info.FilePath), progress);
320+
// Updates progress/speed and notifies the downloads UI;
321+
// marks the task completed when the last byte is written
322+
dm.ProgressCallback(info.DownloadedBytes, info.TotalSize);
323+
}
324+
catch
325+
{
326+
// ProgressCallback throws when Cancel/Pause was pressed
327+
_logger.LogInformation("Background download stopped by user: {CacheKey}", info.CacheKey);
328+
info.CancelledByUser = true;
329+
info.CancelledAtUtc = DateTime.UtcNow;
330+
aborted = true;
331+
break;
295332
}
296333
}
297334
catch (Exception ex) when (ex is not OperationCanceledException)
@@ -319,6 +356,12 @@ private async Task DownloadInBackgroundAsync(
319356
info.IsComplete = info.DownloadedBytes >= info.TotalSize;
320357
info.IsDownloading = false;
321358

359+
// Don't leave an incomplete task hanging as "Working" in the downloads list
360+
if (!info.IsComplete && dm.state == StateTask.Working)
361+
{
362+
dm.Cancel();
363+
}
364+
322365
_logger.LogInformation("Background download {Status}: {FileName} ({Downloaded}/{Total} bytes)",
323366
info.IsComplete ? "complete" : "incomplete",
324367
Path.GetFileName(info.FilePath),
@@ -342,6 +385,15 @@ private async Task DownloadInBackgroundAsync(
342385
}
343386
}
344387

388+
// A user cancel blocks automatic restarts for this long; afterwards a new
389+
// playback of the file starts caching again
390+
private static readonly TimeSpan USER_CANCEL_TTL = TimeSpan.FromHours(1);
391+
392+
private static bool IsUserCancelActive(ProgressiveDownloadInfo info)
393+
{
394+
return info.CancelledByUser && DateTime.UtcNow - info.CancelledAtUtc < USER_CANCEL_TTL;
395+
}
396+
345397
/// <summary>
346398
/// Ordered list of Telegram message IDs that make up a file: a single message for
347399
/// regular files, several (one per part) for split files.

0 commit comments

Comments
 (0)