167 lines
5.3 KiB
C#
167 lines
5.3 KiB
C#
|
|
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<Update?> 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<bool> DownloadAsync(Update update, Func<double, Task>? 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<long>(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<long> 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);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|