-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbrew-style-downloads.cs
More file actions
117 lines (92 loc) · 3.39 KB
/
brew-style-downloads.cs
File metadata and controls
117 lines (92 loc) · 3.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#:package PrettyConsole@6.0.0
using PrettyConsole;
bool keepProgressOutput = true;
var downloads = BrewStyleDownloads.CreateDownloadTasks();
var count = downloads.Count;
var spinner = Spinner.Patterns.Braille;
var spinnerLength = spinner.Count;
int spinnerIndex = 0;
var bufferWidth = Console.BufferWidth;
var task = Task.WhenAll(downloads.Select(BrewStyleDownloads.AdvanceDownload));
Console.CursorVisible = false; // Hide cursor to see progress better
while (!task.IsCompleted) {
spinnerIndex = (spinnerIndex + 1) % spinnerLength;
Console.Overwrite(() => {
foreach (var download in downloads) {
int written = 0;
if (download.IsComplete) {
written += Console.WriteInterpolated(OutputPipe.Error, $"{Color.Green}✔︎{Color.Default} {download.Name}") - 1;
// I remove 1 from written here because "✔︎" is 2 characters long but renders a single block in the terminal
} else {
written += Console.WriteInterpolated(OutputPipe.Error, $"{Color.Green}{spinner[spinnerIndex]}{Color.Default} {download.Name}");
}
var current = (double)download.BytesDownloaded;
var total = (double)download.FileSize;
var padding = bufferWidth - 34 - written;
// progress section takes 34 characters - found by measuring in test run
// var progressLength = Console.WriteLineInterpolated(... without WhiteSpace)
Console.WriteLineInterpolated(OutputPipe.Error, $"{new WhiteSpace(padding)}[Downloaded {current,10:bytes}/{total,10:bytes}]");
}
}, count);
await Task.Delay(25);
}
Console.CursorVisible = true; // restore cursor visibility
if (keepProgressOutput) {
Console.SkipLines(count);
Console.WriteLine(); // Add a newline to separate progress from future outputs
} else {
Console.ClearNextLines(count, OutputPipe.Error);
}
Console.WriteLineInterpolated($"{Color.Green}Done!");
/// <summary>
/// Represents a single download in a "brew" style feed with progress tracking.
/// </summary>
sealed class DownloadStyleTask {
public DownloadStyleTask(string name, long fileSize) {
Name = name;
FileSize = fileSize;
}
public string Name { get; }
public long BytesDownloaded { get; private set; }
public bool IsComplete { get; private set; }
public long FileSize { get; }
/// <summary>
/// Advance the download and notify any listeners.
/// </summary>
public void Advance(long bytes) {
if (bytes <= 0 || IsComplete) {
return;
}
var updated = BytesDownloaded + bytes;
BytesDownloaded = updated >= FileSize ? FileSize : updated;
if (BytesDownloaded == FileSize) IsComplete = true;
}
}
static class BrewStyleDownloads
{
/// <summary>
/// Creates sample download tasks you can feed into a renderer or progress loop.
/// Each task exposes an event so a UI can react when progress changes.
/// </summary>
public static List<DownloadStyleTask> CreateDownloadTasks() {
var tasks = new List<DownloadStyleTask>
{
new("git", 48_000_000),
new("curl", 27_500_000),
new("openssl", 96_000_000),
new("python@3.13", 121_000_000)
};
return tasks;
}
public static async Task AdvanceDownload(DownloadStyleTask task) {
const long minChunkSize = 1_000_000;
while (task.BytesDownloaded < task.FileSize) {
var chunk = Random.Shared.NextInt64(minChunkSize, 2 * minChunkSize);
var remaining = task.FileSize - task.BytesDownloaded;
var current = Math.Min(chunk, remaining);
task.Advance(current);
var delay = Random.Shared.Next(150, 250);
await Task.Delay(delay);
}
}
}