using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Net; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using Newtonsoft.Json; namespace UptimeKuma { public partial class DownloadForm : Form { private readonly Queue downloadQueue = new(); private readonly WebClient webClient = new(); private DownloadItem currentDownloadItem; public DownloadForm() { InitializeComponent(); } private void DownloadForm_Load(object sender, EventArgs e) { webClient.DownloadProgressChanged += DownloadProgressChanged; webClient.DownloadFileCompleted += DownloadFileCompleted; label.Text = "Reading latest version..."; // Read json from https://uptime.kuma.pet/version var versionObj = JsonConvert.DeserializeObject(new WebClient().DownloadString("https://uptime.kuma.pet/version")); var nodeVersion = versionObj.nodejs; var uptimeKumaVersion = versionObj.latest; if (!Directory.Exists("node")) { downloadQueue.Enqueue(new DownloadItem { URL = $"https://nodejs.org/dist/v{nodeVersion}/node-v{nodeVersion}-win-x64.zip", Filename = "node.zip", TargetFolder = "node" }); } if (!Directory.Exists("node")) { downloadQueue.Enqueue(new DownloadItem { URL = $"https://github.com/louislam/uptime-kuma/archive/refs/tags/{uptimeKumaVersion}.zip", Filename = "core.zip", TargetFolder = "core" }); } DownloadNextFile(); } void DownloadNextFile() { if (downloadQueue.Count > 0) { var item = downloadQueue.Dequeue(); currentDownloadItem = item; // Download if the zip file is not existing if (!File.Exists(item.Filename)) { label.Text = item.URL; webClient.DownloadFileAsync(new Uri(item.URL), item.Filename); } else { progressBar.Value = 100; label.Text = "Use local " + item.Filename; DownloadFileCompleted(null, null); } } else { npmSetup(); } } void npmSetup() { labelData.Text = ""; var startInfo = new ProcessStartInfo { FileName = "cmd.exe", Arguments = "run setup", RedirectStandardOutput = false, RedirectStandardError = false, RedirectStandardInput = true, UseShellExecute = false, CreateNoWindow = false, WorkingDirectory = "core" }; var process = new Process(); process.StartInfo = startInfo; process.EnableRaisingEvents = true; process.Exited += (object _, EventArgs e) => { progressBar.Value = 100; if (process.ExitCode == 0) { Task.Delay(2000).ContinueWith((task) => { Application.Restart(); }); label.Text = "Done"; } else { label.Text = "Failed, exit code: " + process.ExitCode; } }; process.Start(); label.Text = "Installing dependencies and download dist files"; progressBar.Value = 50; process.StandardInput.WriteLine("\"../node/npm\" ci --production"); process.StandardInput.WriteLine("\"../node/npm\" run download-dist"); process.StandardInput.WriteLine("exit"); } void DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) { progressBar.Value = e.ProgressPercentage; var total = e.TotalBytesToReceive / 1024; var current = e.BytesReceived / 1024; if (total > 0) { labelData.Text = $"{current}KB/{total}KB"; } } void DownloadFileCompleted(object sender, AsyncCompletedEventArgs e) { Extract(currentDownloadItem); DownloadNextFile(); } void Extract(DownloadItem item) { if (Directory.Exists(item.TargetFolder)) { var dir = new DirectoryInfo(item.TargetFolder); dir.Delete(true); } if (Directory.Exists("temp")) { var dir = new DirectoryInfo("temp"); dir.Delete(true); } labelData.Text = $"Extracting {item.Filename}..."; ZipFile.ExtractToDirectory(item.Filename, "temp"); string[] dirList; // Move to the correct level dirList = Directory.GetDirectories("temp"); if (dirList.Length > 0) { var dir = dirList[0]; // As sometime ExtractToDirectory is still locking the directory, loop until ok while (true) { try { Directory.Move(dir, item.TargetFolder); break; } catch (Exception exception) { Thread.Sleep(1000); } } } else { MessageBox.Show("Unexcepted Error: Cannot move extracted files, folder not found."); } labelData.Text = $"Extracted"; if (Directory.Exists("temp")) { var dir = new DirectoryInfo("temp"); dir.Delete(true); } File.Delete(item.Filename); } } public class DownloadItem { public string URL { get; set; } public string Filename { get; set; } public string TargetFolder { get; set; } } }