using Discovery.Loader.Models; using System; using System.Diagnostics; using System.IO.Compression; using System.Net.Http.Json; using System.Threading; namespace Discovery.Loader.Services; public class UpdateService { public DirectoryInfo AppDir { get; private set; } public UpdateService() { var localApps = new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)); AppDir = new(Path.Combine(localApps.FullName + @"\Discovery")); } public bool IsInstalled() { if (!AppDir.Exists) { return false; } if (!AppDir.EnumerateFiles().Where(p => p.Name.Contains("Discovery.exe", StringComparison.InvariantCultureIgnoreCase)).Any()) { return false; } return true; } public async Task GetUpdateAsync() { using var httpClient = new HttpClient(); var update = await httpClient.GetFromJsonAsync( "https://raw.githubusercontent.com/vaitr/discovery/refs/heads/main/version.json", UpdateContext.Default.Update, default); if (update == null) { Console.WriteLine("Source not available"); return null; } return update; } public bool CompareVersion(Update update) { Console.WriteLine(update.Version); Console.WriteLine(update.Link); var mainExecutable = new FileInfo(Path.Combine(AppDir.FullName + @"/discovery.exe")); if (!mainExecutable.Exists) return false; var currentVersion = FileVersionInfo.GetVersionInfo(mainExecutable.FullName).FileVersion; var vCurrent = Version.Parse(currentVersion!); var vUpdate = Version.Parse(update.Version); if (vUpdate > vCurrent) return false; return true; } public async Task DownloadAsync(Update update, Func? onProgress) { try { using var httpClient = new HttpClient(); httpClient.Timeout = TimeSpan.FromMinutes(3); using var fileStream = new FileStream(Path.Combine(AppDir.FullName + @"/update.zip"), FileMode.Create); // Get the http headers first to examine the content length using var response = await httpClient.GetAsync(update.Link, HttpCompletionOption.ResponseHeadersRead); var contentLength = response.Content.Headers.ContentLength; using var download = await response.Content.ReadAsStreamAsync(default); if (onProgress == null || !contentLength.HasValue) { await download.CopyToAsync(fileStream); return true; } // Convert absolute progress (bytes downloaded) into relative progress (0% - 100%) var relativeProgress = new Progress(async (totalBytes) => { Console.WriteLine(totalBytes + " / " + contentLength.Value + " / " + (float)totalBytes / contentLength.Value); await onProgress((float)totalBytes / contentLength.Value); }); // Use extension method to report progress while downloading await download.CopyToAsync(fileStream, 81920, relativeProgress, default); return true; } catch { return false; } } public void Extract() { var archivePath = Path.Combine(AppDir.FullName + @"/update.zip"); using (var archive = ZipFile.OpenRead(archivePath)) { foreach (var entry in archive.Entries) { var file = new FileInfo(Path.Combine(AppDir.FullName, entry.Name)); if (file.Exists) file.Delete(); entry.ExtractToFile(file.FullName); } } File.Delete(archivePath); } public void StartExecutable() { var exec = new FileInfo(Path.Combine(AppDir.FullName + @"/discovery.exe")); _ = Process.Start(new ProcessStartInfo { UseShellExecute = true, FileName = exec.FullName }); } } public static class StreamExtensions { public static async Task CopyToAsync(this Stream source, Stream destination, int bufferSize, IProgress progress = null, CancellationToken cancellationToken = default) { if (source == null) throw new ArgumentNullException(nameof(source)); if (!source.CanRead) throw new ArgumentException("Has to be readable", nameof(source)); if (destination == null) throw new ArgumentNullException(nameof(destination)); if (!destination.CanWrite) throw new ArgumentException("Has to be writable", nameof(destination)); if (bufferSize < 0) throw new ArgumentOutOfRangeException(nameof(bufferSize)); var buffer = new byte[bufferSize]; long totalBytesRead = 0; int bytesRead; while ((bytesRead = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0) { await destination.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false); totalBytesRead += bytesRead; progress?.Report(totalBytesRead); } } }