LINQPad's .NET downloader always manages to detect the latest version and downloads quickly. I'm not sure how it's implemented.
I found the source code of the downloader using ILSpy.
Accessing Line https://api.nuget.org/v3/index.json will automatically switch to CDN Line https://nuget.cdn.azure.cn/v3/index.json
I found the source code of the downloader using ILSpy.
// NetCoreDownloader.MainWindow
using System;
using System.CodeDom.Compiler;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Markup;
using NetCoreDownloader;
public class MainWindow : Window, IComponentConnector
{
private bool _isBeta;
private bool _is32Bit;
private bool _isArm64;
private string _x64Uri;
private string _x86Uri;
private CancellationTokenSource _cancelSource;
internal TextBlock x64Text;
internal TextBox txtX64Version;
internal TextBox txtX86Version;
internal TextBox txtX64Status;
internal TextBox txtX86Status;
internal TextBox txtDownloadSource;
internal Grid grdDownloadButtons;
internal Button btnDownloadX64;
internal Button btnDownloadX86;
internal GroupBox grpProgress;
internal TextBlock lblAction;
internal Button btnCancel;
internal ProgressBar progressBar;
internal TextBlock txtProgress;
internal GroupBox grpLaunch;
internal Button btnLaunchLINQPadX64;
internal Button btnLaunchLINQPadX86;
private bool _contentLoaded;
public MainWindow()
{
_isBeta = File.Exists(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "BetaUpdateStream"));
_is32Bit = FxChecker.IsX86;
_isArm64 = FxChecker.IsArm64;
InitializeComponent();
base.Title = base.Title + " (LINQPad 8.0.18" + (_isBeta ? ", beta update stream" : "") + ")";
if (_isArm64)
{
x64Text.Text = "ARM64 (64-bit)";
btnDownloadX64.Content = "Download Runtime - ARM64";
btnLaunchLINQPadX64.Content = "Launch LINQPad (ARM64)";
}
grpProgress.Visibility = Visibility.Hidden;
if (_is32Bit)
{
foreach (UIElement child in grdDownloadButtons.Children)
{
if (child is TextBlock || child is Label)
{
child.Visibility = Visibility.Collapsed;
}
}
}
PopulateDownloadSource();
PopulateStatus();
}
private string GetLINQPadPath(bool x86, bool cmd)
{
string text = (cmd ? "LPRun8" : "LINQPad8");
string text2 = ((x86 || _is32Bit) ? "x86" : (_isArm64 ? "arm64" : "x64"));
string text3 = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, text + "-" + text2 + ".exe");
if (File.Exists(text3))
{
return text3;
}
text3 = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, text + ".exe");
if (File.Exists(text3) && GetArchitectureFromExe(text3) == text2)
{
return text3;
}
return "executable";
}
public static string GetArchitectureFromExe(string path)
{
byte[] array = new byte[4096];
using (FileStream fileStream = File.OpenRead(path))
{
if (fileStream.Length < 4096)
{
return null;
}
fileStream.Read(array, 0, 4096);
}
int num = BitConverter.ToInt32(array, 60);
if (num < 0 || num + 4 > 4090)
{
return null;
}
return BitConverter.ToUInt16(array, num + 4) switch
{
332 => "x86",
43620 => "arm64",
34404 => "x64",
_ => null,
};
}
private async void PopulateStatus()
{
while (true)
{
string text3 = (txtX64Version.Text = (txtX86Version.Text = "?"));
SemanticVersion semanticVersion;
SemanticVersion semanticVersion2;
try
{
semanticVersion = FxChecker.FindHighestValidVersion(for64bit: true);
semanticVersion2 = FxChecker.FindHighestValidVersion(for64bit: false);
}
catch
{
break;
}
txtX64Version.Text = ((semanticVersion != null) ? ("Version " + semanticVersion?.ToString() + " installed") : "Not installed");
txtX86Version.Text = ((semanticVersion2 != null) ? ("Version " + semanticVersion2?.ToString() + " installed") : "Not installed");
PopulateStatusBox(txtX64Status, semanticVersion);
PopulateStatusBox(txtX86Status, semanticVersion2);
btnLaunchLINQPadX64.IsEnabled = FxChecker.MinVersion.CompareTo(semanticVersion) <= 0 && !_is32Bit;
btnLaunchLINQPadX86.IsEnabled = FxChecker.MinVersion.CompareTo(semanticVersion2) <= 0;
await Task.Delay(1000);
}
}
private void PopulateStatusBox(TextBox box, SemanticVersion version)
{
if (version == null)
{
box.Text = "";
return;
}
try
{
if (version.CompareTo(FxChecker.MinVersion) < 0)
{
box.Text = "Update required";
}
else if (version.CompareTo(FxChecker.MinRecommendedVersion) < 0)
{
box.Text = "Newer version available";
}
else
{
box.Text = "You're up to date!";
}
}
catch
{
}
}
private async void PopulateDownloadSource()
{
Task<byte[]> task = new TapWebClient().DownloadDataAsync("https://api.nuget.org/v3/index.json");
task.ContinueWith((Task<byte[]> t) => t.Exception);
bool flag = await Task.WhenAny(task, Task.Delay(8000)) == task && !task.IsFaulted;
string text;
try
{
text = await new TapWebClient().DownloadStringAsync("https://www.linqpad.net/FxDownloadSource.8.0." + (flag ? "desktop" : "sdk") + ".txt");
}
catch (Exception ex)
{
txtDownloadSource.Text = "Cannot contact server: " + ex.Message;
return;
}
string[] array = (from s in text.Split(new string[1] { "\n" }, StringSplitOptions.RemoveEmptyEntries)
select s.Trim()).ToArray();
if (array.Length < 3)
{
txtDownloadSource.Text = "Invalid response from server.";
return;
}
txtDownloadSource.Text = array[0];
FxChecker.MinRecommendedVersion = SemanticVersion.TryParse(array[0]) ?? FxChecker.MinRecommendedVersion;
_x64Uri = (FxChecker.IsArm64 ? array[3] : array[1]);
_x86Uri = array[2];
btnDownloadX64.IsEnabled = !_is32Bit;
btnDownloadX86.IsEnabled = true;
if (App.AutoDownload)
{
Download(_is32Bit ? _x86Uri : _x64Uri);
}
}
private void DownloadX64(object sender, RoutedEventArgs e)
{
Download(_x64Uri);
}
private void DownloadX86(object sender, RoutedEventArgs e)
{
Download(_x86Uri);
}
private void BtnCancel_Click(object sender, RoutedEventArgs e)
{
_cancelSource.Cancel();
}
private async void LlRefresh_Click(object sender, RoutedEventArgs e)
{
TextBox textBox = txtX64Version;
TextBox textBox2 = txtX86Version;
TextBox textBox3 = txtX64Status;
string text2 = (txtX86Status.Text = "");
string text4 = (textBox3.Text = text2);
string text7 = (textBox.Text = (textBox2.Text = text4));
await Task.Delay(300);
PopulateStatus();
}
private void BtnLaunchLINQPadX64_Click(object sender, RoutedEventArgs e)
{
LaunchLINQPad(x86: false);
}
private void BtnLaunchLINQPadX86_Click(object sender, RoutedEventArgs e)
{
LaunchLINQPad(x86: true);
}
private void Hyperlink_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("The X86 runtime lets you run LINQPad8-x86.exe, which launches LINQPad as a 32-bit process.\r\n\r\nThis is required when you need to reference 32-bit native DLLs.");
}
private void LaunchLINQPad(bool x86)
{
string lINQPadPath = GetLINQPadPath(x86, cmd: false);
if (!File.Exists(lINQPadPath))
{
MessageBox.Show("Cannot locate " + lINQPadPath + ".");
return;
}
string lINQPadPath2 = GetLINQPadPath(x86, cmd: true);
if (!File.Exists(lINQPadPath2))
{
MessageBox.Show("Cannot locate " + lINQPadPath2 + ".");
return;
}
using (Process process = Process.Start(new ProcessStartInfo(lINQPadPath2)
{
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}))
{
StringBuilder errors = new StringBuilder();
process.ErrorDataReceived += delegate(object sender, DataReceivedEventArgs errorArgs)
{
errors.AppendLine(errorArgs.Data);
};
process.BeginErrorReadLine();
int num = 0;
while (process.StandardOutput.ReadLine() != null)
{
num++;
}
int exitCode = process.ExitCode;
if (exitCode != 0 && exitCode != 1)
{
string text = "Unable to start LINQPad.";
if (errors.Length > 0)
{
text = text + "\r\n\r\n" + errors.ToString();
}
MessageBox.Show(text, "LINQPad", MessageBoxButton.OK, MessageBoxImage.Hand);
return;
}
}
Process.Start(new ProcessStartInfo(lINQPadPath)
{
UseShellExecute = true
}).Dispose();
}
private async void Download(string uri)
{
Button button = btnDownloadX64;
bool isEnabled = (btnDownloadX86.IsEnabled = false);
button.IsEnabled = isEnabled;
grpProgress.Visibility = Visibility.Visible;
_cancelSource = new CancellationTokenSource();
btnCancel.Visibility = Visibility.Visible;
progressBar.IsEnabled = true;
txtProgress.Text = "Connecting...";
try
{
string filename = new Uri(uri).Segments.Last();
lblAction.Text = "Downloading " + filename;
string tempFile = Path.Combine(Path.GetTempPath(), filename);
if (File.Exists(tempFile))
{
File.Delete(tempFile);
}
Progress<DownloadProgressChangedEventArgs> progress = new Progress<DownloadProgressChangedEventArgs>(delegate(DownloadProgressChangedEventArgs p)
{
progressBar.Value = p.ProgressPercentage;
txtProgress.Text = p.BytesReceived / 1000000 + "MB of " + (p.TotalBytesToReceive / 1000000 + 1) + "MB";
});
await new TapWebClient().DownloadFileAsync(uri, tempFile, _cancelSource.Token, progress);
txtProgress.Text = "Complete";
progressBar.Value = 100.0;
Process.Start(tempFile);
lblAction.Text = "Running " + filename;
}
catch when (_cancelSource.IsCancellationRequested)
{
txtProgress.Text = "";
progressBar.Value = 0.0;
lblAction.Text = "Operation canceled by user.";
}
catch (Exception ex)
{
txtProgress.Text = "Error: downloading via web browser...";
lblAction.Text = ex.Message;
progressBar.Value = 0.0;
await Task.Delay(2000);
Process.Start(new ProcessStartInfo(uri)
{
UseShellExecute = true
});
}
finally
{
btnDownloadX64.IsEnabled = !_is32Bit;
btnDownloadX86.IsEnabled = true;
btnCancel.Visibility = Visibility.Collapsed;
progressBar.IsEnabled = false;
}
}
[DebuggerNonUserCode]
[GeneratedCode("PresentationBuildTasks", "4.0.0.0")]
public void InitializeComponent()
{
if (!_contentLoaded)
{
_contentLoaded = true;
Uri resourceLocator = new Uri("/Download .NET;component/mainwindow.xaml", UriKind.Relative);
Application.LoadComponent(this, resourceLocator);
}
}
[DebuggerNonUserCode]
[GeneratedCode("PresentationBuildTasks", "4.0.0.0")]
[EditorBrowsable(EditorBrowsableState.Never)]
void IComponentConnector.Connect(int connectionId, object target)
{
switch (connectionId)
{
case 1:
x64Text = (TextBlock)target;
break;
case 2:
txtX64Version = (TextBox)target;
break;
case 3:
txtX86Version = (TextBox)target;
break;
case 4:
txtX64Status = (TextBox)target;
break;
case 5:
txtX86Status = (TextBox)target;
break;
case 6:
txtDownloadSource = (TextBox)target;
break;
case 7:
grdDownloadButtons = (Grid)target;
break;
case 8:
btnDownloadX64 = (Button)target;
btnDownloadX64.Click += DownloadX64;
break;
case 9:
btnDownloadX86 = (Button)target;
btnDownloadX86.Click += DownloadX86;
break;
case 10:
((Hyperlink)target).Click += Hyperlink_Click;
break;
case 11:
grpProgress = (GroupBox)target;
break;
case 12:
lblAction = (TextBlock)target;
break;
case 13:
btnCancel = (Button)target;
btnCancel.Click += BtnCancel_Click;
break;
case 14:
progressBar = (ProgressBar)target;
break;
case 15:
txtProgress = (TextBlock)target;
break;
case 16:
grpLaunch = (GroupBox)target;
break;
case 17:
btnLaunchLINQPadX64 = (Button)target;
btnLaunchLINQPadX64.Click += BtnLaunchLINQPadX64_Click;
break;
case 18:
btnLaunchLINQPadX86 = (Button)target;
btnLaunchLINQPadX86.Click += BtnLaunchLINQPadX86_Click;
break;
default:
_contentLoaded = true;
break;
}
}
}
Accessing Line https://api.nuget.org/v3/index.json will automatically switch to CDN Line https://nuget.cdn.azure.cn/v3/index.json