using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Drawing; using System.IO; using System.IO.Compression; using System.IO.Pipes; using System.Linq; using System.Net; using System.Net.Http; using System.Reflection; using System.Threading.Tasks; using Microsoft.VisualBasic.CompilerServices; using Pilz.Updating.UpdateInstaller; namespace Pilz.Updating { public class UpdateClient { // E b v e n t s public event UpdateStatusChangedEventHandler UpdateStatusChanged; public delegate void UpdateStatusChangedEventHandler(UpdateStatus newStatus); public event DownloadingUpdateEventHandler DownloadingUpdate; public delegate void DownloadingUpdateEventHandler(UpdatePackageInfo pkg, CancelEventArgs e); public event InstallingUpdateEventHandler InstallingUpdate; public delegate void InstallingUpdateEventHandler(UpdatePackageInfo pkg, CancelEventArgs e); public event UpdateInstallerStartedEventHandler UpdateInstallerStarted; public delegate void UpdateInstallerStartedEventHandler(); public event NoUpdatesFoundEventHandler NoUpdatesFound; public delegate void NoUpdatesFoundEventHandler(); // F i e l d s private readonly Dictionary dicPackagePaths = new Dictionary(); private UpdateStatus curDownloadingStatus = UpdateStatus.Waiting; // P r o p e r t i e s public HttpClient WebClient { get; private set; } = new(); public string UpdateUrl { get; private set; } public ApplicationVersion CurrentVersion { get; private set; } public Channels MinimumChannel { get; private set; } public UpdateInfo UpdateInfo { get; private set; } = null; public UpdatePackageInfo UpdatePackageInfo { get; private set; } = null; public bool AutoCloseHostApplication { get; set; } = false; public bool AutoRestartHostApplication { get; set; } = false; public string RestartHostApplicationArguments { get; set; } public string HostApplicationPath { get; set; } = string.Empty; public string ApplicationName { get; set; } = string.Empty; public bool InstallAsAdmin { get; set; } = false; public uint MillisecondsToWaitForHostApplicationToClose { get; set; } = 10000; public bool ForceClosingHostApplication { get; set; } = true; public bool UIDarkMode { get; set; } = false; // C o n s t r u c t o r s public UpdateClient(string updateUrl, ApplicationVersion currentVersion, Channels minimumChannel) { UpdateUrl = updateUrl; CurrentVersion = currentVersion; MinimumChannel = (Channels)Math.Max((int)minimumChannel, (int)currentVersion.Channel); } // E v e n t M e t h o d s private bool RaiseDownloadingUpdate(UpdatePackageInfo pkg) { var e = new CancelEventArgs(false); DownloadingUpdate?.Invoke(pkg, e); return e.Cancel; } private bool RaiseInstallingUpdate(UpdatePackageInfo pkg) { var e = new CancelEventArgs(true); InstallingUpdate?.Invoke(pkg, e); return e.Cancel; } // U p d a t e R o u t i n e s public async Task UpdateInteractive() { var latestVersion = await CheckForUpdate(); if (latestVersion is null) NoUpdatesFound?.Invoke(); else await UpdateInteractive(latestVersion); } public async Task UpdateInteractive(UpdatePackageInfo package) { if (!RaiseDownloadingUpdate(package) && await DownloadPackageAsync(package)) { if (!RaiseInstallingUpdate(package)) await InstallPackage(package); } } public void RaiseUpdateStatusChanged(UpdateStatus newStatus) { UpdateStatusChanged?.Invoke(newStatus); } // F e a t u r e s public async Task GetUpdateInfo() { string str = await WebClient.GetStringAsync(UpdateUrl); var info = UpdateInfo.Parse(str); return info; } public async Task CheckForUpdate() { RaiseUpdateStatusChanged(UpdateStatus.Searching); UpdateInfo = await GetUpdateInfo(); if (UpdateInfo is not null) return CheckForUpdate(UpdateInfo); else return null; } public UpdatePackageInfo CheckForUpdate(UpdateInfo updateInfo) { UpdatePackageInfo foundPkgInfo = null; var latestVersion = CurrentVersion; RaiseUpdateStatusChanged(UpdateStatus.Searching); foreach (UpdatePackageInfo pkgInfo in updateInfo.Packages) { if (pkgInfo.Version.Channel <= MinimumChannel && pkgInfo.Version > latestVersion) { foundPkgInfo = pkgInfo; latestVersion = pkgInfo.Version; } } UpdatePackageInfo = foundPkgInfo; return foundPkgInfo; } public async Task DownloadPackageAsync(UpdatePackageInfo package) { curDownloadingStatus = UpdateStatus.DownloadingPackage; RaiseUpdateStatusChanged(curDownloadingStatus); string dirPath = Path.Combine(MyPaths.GetMyAppDataPath(), Conversions.ToString(package.GetHashCode())); string zipPath = Path.Combine(dirPath, PackageFileNameDefinations.ZIP_PACKAGE_FILENAME); var dir = new DirectoryInfo(dirPath); try { // Ensure existing and empty directory for the Zip File if (dir.Exists) dir.Delete(true); dir.Create(); // Download zip package using var zipFile = new FileStream(zipPath, FileMode.Create, FileAccess.ReadWrite); using var zipStream = await WebClient.GetStreamAsync(package.Packagelink); await zipStream.CopyToAsync(zipFile); // Remember path to package directory dicPackagePaths.Add(package, dirPath); } catch (Exception) { return false; } return true; } private async Task DownloadUpdateInstaller() { curDownloadingStatus = UpdateStatus.DownloadingInstaller; RaiseUpdateStatusChanged(curDownloadingStatus); // Ensure update installer path is empty var installerDirPath = new DirectoryInfo(Path.Combine(MyPaths.GetMyAppDataPath(), "UpdateInstallerTool")); if (installerDirPath.Exists) installerDirPath.Delete(true); await Task.Delay(100); installerDirPath.Create(); await Task.Delay(100); // Download update installer zip var installerZipPath = Path.Combine(installerDirPath.FullName, "UpdatenInstaller.zip"); using (var installerZipFile = new FileStream(installerZipPath, FileMode.Create, FileAccess.ReadWrite)) { using var installerZipStream = await WebClient.GetStreamAsync(UpdateInfo.UpdateInstallerLink); await installerZipStream.CopyToAsync(installerZipFile); } // Extract update installer var installerExtractPath = installerDirPath.CreateSubdirectory("extracted"); ZipFile.ExtractToDirectory(installerZipPath, installerExtractPath.FullName); File.Delete(installerZipPath); // Get UpdateInstaller.exe file return installerExtractPath.EnumerateFiles("*.exe").FirstOrDefault(); } private void StartUpdateInstaller(string packagePath, string installerPath) { RaiseUpdateStatusChanged(UpdateStatus.StartingInstaller); // Create update settings var myAppPath = IO.Extensions.GetExecutablePath(); var updateConfig = new UpdateInstallerConfig { PackagePath = packagePath, RestartHostApplication = AutoRestartHostApplication, RestartHostApplicationArguments = AutoRestartHostApplication ? RestartHostApplicationArguments : string.Empty, ApplicationName = ApplicationName, HostApplicationPath = string.IsNullOrEmpty(HostApplicationPath) ? Path.GetDirectoryName(myAppPath) : HostApplicationPath, HostApplicationProcessPath = myAppPath, MillisecondsToWaitForHostApplicationToClose = MillisecondsToWaitForHostApplicationToClose, ForceClosingHostApplication = ForceClosingHostApplication, UIDarkMode = UIDarkMode }; // Start UpdateInstaller var procStartInfo = new ProcessStartInfo { FileName = installerPath, Arguments = updateConfig.ToString(), UseShellExecute = false, Verb = InstallAsAdmin ? "runas" : string.Empty }; Process.Start(procStartInfo); UpdateInstallerStarted?.Invoke(); } public async Task InstallPackage(UpdatePackageInfo package) { if (dicPackagePaths.TryGetValue(package, out var packagePath)) { // Download update installer var installerPath = await DownloadUpdateInstaller(); // Start update installer StartUpdateInstaller(packagePath, installerPath.FullName); // Close Host Application if (AutoCloseHostApplication) Environment.Exit(Environment.ExitCode); return true; } return false; } } }