diff --git a/UniGetUI.iss b/UniGetUI.iss
index 2692996635..50cf1f48ab 100644
--- a/UniGetUI.iss
+++ b/UniGetUI.iss
@@ -1,7 +1,7 @@
; Script generated by the Inno Setup Script Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
-#define MyAppVersion "3.1.2"
+#define MyAppVersion "3.1.4-beta1"
#define MyAppName "UniGetUI (formerly WingetUI)"
#define MyAppPublisher "Martí Climent"
#define MyAppURL "https://github.com/marticliment/UniGetUI"
@@ -23,7 +23,7 @@ AppPublisher={#MyAppPublisher}
AppPublisherURL="https://www.marticliment.com/unigetui/"
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
-VersionInfoVersion=3.1.2.0
+VersionInfoVersion=3.1.4.0
DefaultDirName="{autopf64}\UniGetUI"
DisableProgramGroupPage=yes
DisableDirPage=no
diff --git a/scripts/BuildNumber b/scripts/BuildNumber
new file mode 100644
index 0000000000..d7765fe47e
--- /dev/null
+++ b/scripts/BuildNumber
@@ -0,0 +1 @@
+70
\ No newline at end of file
diff --git a/scripts/apply_versions.py b/scripts/apply_versions.py
index 1d29718d66..42e821ffb2 100644
--- a/scripts/apply_versions.py
+++ b/scripts/apply_versions.py
@@ -4,14 +4,26 @@
os.chdir(os.path.join(os.path.dirname(__file__), "..")) # move to root project
try:
- floatval = input("Enter version code (X.XXX) : ")
+ # floatval = input("Enter version code (X.XXX) : ")
+ # versionCode = float(floatval)
+ versionName = str(input("Enter version name (string) : "))
- if floatval == "":
+ if versionName == "":
print("Version changer script aborted")
exit()
+
+ BuildNumber = -1
+ c = ""
+ if os.path.exists("scripts/BuildNumber"):
+ with open("scripts/BuildNumber", "r") as f:
+ c = f.read()
+
+ BuildNumber = int(c) if c != "" else int(input("Build number file was empty. Insert (integer) build number: "))-1
+ print(f"Build number set to {BuildNumber+1}")
+ with open("scripts/BuildNumber", "w") as f:
+ f.write(str(BuildNumber+1))
+
- versionCode = float(floatval)
- versionName = str(input("Enter version name (string) : "))
versionISS = str(input("Enter version (X.X.X.X) : "))
def fileReplaceLinesWith(filename: str, list: dict[str, str], encoding="utf-8"):
@@ -32,7 +44,7 @@ def fileReplaceLinesWith(filename: str, list: dict[str, str], encoding="utf-8"):
fileReplaceLinesWith("src/UniGetUI.Core.Data/CoreData.cs", {
" public const string VersionName = ": f" \"{versionName}\"; // Do not modify this line, use file scripts/apply_versions.py\n",
- " public const double VersionNumber = ": f" {versionCode}; // Do not modify this line, use file scripts/apply_versions.py\n",
+ " public const int BuildNumber = ": f" {BuildNumber+1}; // Do not modify this line, use file scripts/apply_versions.py\n",
}, encoding="utf-8-sig")
fileReplaceLinesWith("src/SharedAssemblyInfo.cs", {
diff --git a/scripts/translation_utils.py b/scripts/translation_utils.py
index 1faec50a2d..4df41371d3 100644
--- a/scripts/translation_utils.py
+++ b/scripts/translation_utils.py
@@ -50,6 +50,8 @@ def get_all_strings():
r'<[a-zA-Z0-9]+:ButtonCard' + MAIN_WILDCARD + r'+Text=["\'].+["\']' + MAIN_WILDCARD + r'*\/?>': lambda match: match.split(" Text=\"")[1].split("\"")[0].encode('raw_unicode_escape').decode('unicode_escape'),
r'<[a-zA-Z0-9]+:ButtonCard' + MAIN_WILDCARD + r'+ButtonText=["\'].+["\']' + MAIN_WILDCARD + r'*\/?>': lambda match: match.split(" ButtonText=\"")[1].split("\"")[0].encode('raw_unicode_escape').decode('unicode_escape'),
r'<[a-zA-Z0-9]+:CheckboxCard' + MAIN_WILDCARD + r'+Text=["\'].+["\']' + MAIN_WILDCARD + r'*\/?>': lambda match: match.split(" Text=\"")[1].split("\"")[0].encode('raw_unicode_escape').decode('unicode_escape'),
+ r'<[a-zA-Z0-9]+:CheckboxButtonCard' + MAIN_WILDCARD + r'+CheckboxText=["\'].+["\']' + MAIN_WILDCARD + r'*\/?>': lambda match: match.split(" Text=\"")[1].split("\"")[0].encode('raw_unicode_escape').decode('unicode_escape'),
+ r'<[a-zA-Z0-9]+:CheckboxButtonCard' + MAIN_WILDCARD + r'+ButtonText=["\'].+["\']' + MAIN_WILDCARD + r'*\/?>': lambda match: match.split(" Text=\"")[1].split("\"")[0].encode('raw_unicode_escape').decode('unicode_escape'),
r'<[a-zA-Z0-9]+:ComboboxCard' + MAIN_WILDCARD + r'+Text=["\'].+["\']' + MAIN_WILDCARD + r'*\/?>': lambda match: match.split(" Text=\"")[1].split("\"")[0].encode('raw_unicode_escape').decode('unicode_escape'),
r'<[a-zA-Z0-9]+:BetterMenuItem' + MAIN_WILDCARD + r'+Text=["\'].+["\']' + MAIN_WILDCARD + r'*\/?>': lambda match: match.split(" Text=\"")[1].split("\"")[0].encode('raw_unicode_escape').decode('unicode_escape'),
r'<[a-zA-Z0-9]+:NavButton' + MAIN_WILDCARD + r'+Text=["\'].+["\']' + MAIN_WILDCARD + r'*\/?>': lambda match: match.split(" Text=\"")[1].split("\"")[0].encode('raw_unicode_escape').decode('unicode_escape'),
diff --git a/src/SharedAssemblyInfo.cs b/src/SharedAssemblyInfo.cs
index 875020225d..5b6f6a6296 100644
--- a/src/SharedAssemblyInfo.cs
+++ b/src/SharedAssemblyInfo.cs
@@ -6,7 +6,7 @@
[assembly: AssemblyTitle("UniGetUI")]
[assembly: AssemblyDefaultAlias("UniGetUI")]
[assembly: AssemblyCopyright("2024, Martí Climent")]
-[assembly: AssemblyVersion("3.1.2.0")]
-[assembly: AssemblyFileVersion("3.1.2.0")]
-[assembly: AssemblyInformationalVersion("3.1.2")]
+[assembly: AssemblyVersion("3.1.4.0")]
+[assembly: AssemblyFileVersion("3.1.4.0")]
+[assembly: AssemblyInformationalVersion("3.1.4-beta1")]
[assembly: SupportedOSPlatform("windows10.0.19041")]
diff --git a/src/UniGetUI.Core.Data.Tests/CoreTests.cs b/src/UniGetUI.Core.Data.Tests/CoreTests.cs
index c49d0b56df..8a9f31509e 100644
--- a/src/UniGetUI.Core.Data.Tests/CoreTests.cs
+++ b/src/UniGetUI.Core.Data.Tests/CoreTests.cs
@@ -23,12 +23,9 @@ public void CheckDirectoryAttributes(string directory)
public void CheckOtherAttributes()
{
Assert.NotEmpty(CoreData.VersionName);
- Assert.NotEqual(0, CoreData.VersionNumber);
+ Assert.NotEqual(0, CoreData.BuildNumber);
Assert.True(File.Exists(CoreData.IgnoredUpdatesDatabaseFile), "The Ignored Updates database file does not exist, but it should have been created automatically.");
- int notif_3 = CoreData.UpdatesAvailableNotificationTag;
- int notif_4 = CoreData.UpdatesAvailableNotificationTag;
- Assert.True(notif_3 == notif_4, "The UpdatesAvailableNotificationId must be always the same");
Assert.NotEqual(0, CoreData.UpdatesAvailableNotificationTag);
Assert.True(Directory.Exists(CoreData.UniGetUIExecutableDirectory), "Directory where the executable is located does not exist");
diff --git a/src/UniGetUI.Core.Data/CoreData.cs b/src/UniGetUI.Core.Data/CoreData.cs
index 96c35c711c..3eb033b815 100644
--- a/src/UniGetUI.Core.Data/CoreData.cs
+++ b/src/UniGetUI.Core.Data/CoreData.cs
@@ -47,7 +47,7 @@ private static int GetCodePage()
}
public const string VersionName = "3.1.4-beta1"; // Do not modify this line, use file scripts/apply_versions.py
- public const double VersionNumber = 3.14; // Do not modify this line, use file scripts/apply_versions.py
+ public const int BuildNumber = 70; // Do not modify this line, use file scripts/apply_versions.py
public const string UserAgentString = $"UniGetUI/{VersionName} (https://marticliment.com/unigetui/; contact@marticliment.com)";
@@ -169,6 +169,7 @@ public static string IgnoredUpdatesDatabaseFile
/// The ID of the notification that is used to inform the user that updates are available
///
public const int UpdatesAvailableNotificationTag = 1234;
+ public const int UniGetUICanBeUpdated = 1235;
///
diff --git a/src/UniGetUI.Core.Tools/Tools.cs b/src/UniGetUI.Core.Tools/Tools.cs
index 7d7ff1e2c6..9e21d427b4 100644
--- a/src/UniGetUI.Core.Tools/Tools.cs
+++ b/src/UniGetUI.Core.Tools/Tools.cs
@@ -2,9 +2,11 @@
using System.Diagnostics;
using System.Globalization;
using System.Net;
+using System.Net.NetworkInformation;
using System.Security.Cryptography;
using System.Security.Principal;
using System.Text;
+using UniGetUI.Core.Classes;
using UniGetUI.Core.Data;
using UniGetUI.Core.Language;
using UniGetUI.Core.Logging;
@@ -542,5 +544,43 @@ public static ProcessStartInfo UpdateEnvironmentVariables(ProcessStartInfo info)
}
return info;
}
+
+
+ ///
+ /// Pings the update server and 3 well-known sites to check for internet availability
+ ///
+ public static async Task WaitForInternetConnection()
+ => await (await TaskRecycler.RunOrAttachAsync(_waitForInternetConnection));
+
+ public static async Task _waitForInternetConnection()
+ {
+ Logger.Debug("Checking for internet connectivity. Pinging google.com, microsoft.com, couldflare.com and marticliment.com");
+ string[] hosts = ["google.com", "microsoft.com", "cloudflare.com", "marticliment.com"];
+ while (true)
+ {
+ foreach (var host in hosts)
+ {
+ using (var pinger = new Ping())
+ {
+ try
+ {
+ PingReply reply = await pinger.SendPingAsync(host, 10);
+ if (reply.Status is IPStatus.Success)
+ {
+ Logger.Debug($"{host} responded successfully to ping, internet connection was validated.");
+ return;
+ }
+
+ Logger.Debug($"Could not ping {host}!");
+ }
+ catch (Exception ex)
+ {
+ Logger.Debug($"Could not ping {host} with error {ex.Message}. Are you connected to the internet?");
+ }
+ }
+ }
+ await Task.Delay(TimeSpan.FromSeconds(5));
+ }
+ }
}
}
diff --git a/src/UniGetUI.Interface.BackgroundApi/BackgroundApi.cs b/src/UniGetUI.Interface.BackgroundApi/BackgroundApi.cs
index fdcefa9fc4..2b3fc55c57 100644
--- a/src/UniGetUI.Interface.BackgroundApi/BackgroundApi.cs
+++ b/src/UniGetUI.Interface.BackgroundApi/BackgroundApi.cs
@@ -174,7 +174,7 @@ public void BuildV1WidgetsApi()
return 401;
}
- return CoreData.VersionNumber.ToString();
+ return CoreData.BuildNumber.ToString();
});
// Return found updates
diff --git a/src/UniGetUI.Interface.Enums/Enums.cs b/src/UniGetUI.Interface.Enums/Enums.cs
index c76b7f8fd6..2992c8f12f 100644
--- a/src/UniGetUI.Interface.Enums/Enums.cs
+++ b/src/UniGetUI.Interface.Enums/Enums.cs
@@ -91,5 +91,6 @@ public class NotificationArguments
public const string Show = "openUniGetUI";
public const string ShowOnUpdatesTab = "openUniGetUIOnUpdatesTab";
public const string UpdateAllPackages = "updateAll";
+ public const string ReleaseSelfUpdateLock = "releaseSelfUpdateLock";
}
}
diff --git a/src/UniGetUI/App.xaml.cs b/src/UniGetUI/App.xaml.cs
index 1dc0c939af..5021f12741 100644
--- a/src/UniGetUI/App.xaml.cs
+++ b/src/UniGetUI/App.xaml.cs
@@ -228,12 +228,6 @@ private async Task LoadComponentsAsync()
{
try
{
- // Run other initializations asynchronously
- if (!Settings.Get("DisableAutoUpdateWingetUI"))
- {
- UpdateUniGetUIIfPossible();
- }
-
IconDatabase.InitializeInstance();
IconDatabase.Instance.LoadIconAndScreenshotsDatabase();
@@ -404,147 +398,6 @@ public async void DisposeAndQuit(int outputCode = 0)
Environment.Exit(outputCode);
}
- private async void UpdateUniGetUIIfPossible(int round = 0)
- {
- InfoBar? banner = null;
- try
- {
- Logger.Debug("Starting update check");
-
- string fileContents;
-
- using (HttpClient client = new(CoreData.GenericHttpClientParameters))
- {
- client.Timeout = TimeSpan.FromSeconds(600);
- client.DefaultRequestHeaders.UserAgent.ParseAdd(CoreData.UserAgentString);
- fileContents = await client.GetStringAsync("https://www.marticliment.com/versions/unigetui.ver");
- }
-
- if (!fileContents.Contains("///"))
- {
- throw new FormatException("The updates file does not follow the FloatVersion///Sha256Hash format");
- }
-
- float LatestVersion = float.Parse(fileContents.Split("///")[0].Replace("\n", "").Trim(), CultureInfo.InvariantCulture);
- string InstallerHash = fileContents.Split("///")[1].Replace("\n", "").Trim().ToLower();
-
- if (LatestVersion > CoreData.VersionNumber)
- {
- Logger.Info("Updates found, downloading installer...");
- Logger.Info("Current version: " + CoreData.VersionNumber.ToString(CultureInfo.InvariantCulture));
- Logger.Info("Latest version : " + LatestVersion.ToString(CultureInfo.InvariantCulture));
-
- banner = MainWindow.UpdatesBanner;
- banner.Title = CoreTools.Translate("WingetUI version {0} is being downloaded.", LatestVersion.ToString(CultureInfo.InvariantCulture));
- banner.Message = CoreTools.Translate("This may take a minute or two");
- banner.Severity = InfoBarSeverity.Informational;
- banner.IsOpen = true;
- banner.IsClosable = false;
-
- Uri DownloadUrl = new("https://github.com/marticliment/WingetUI/releases/latest/download/UniGetUI.Installer.exe");
- string InstallerPath = Path.Join(Directory.CreateTempSubdirectory().FullName, "unigetui-updater.exe");
-
- using (HttpClient client = new(CoreData.GenericHttpClientParameters))
- {
- client.DefaultRequestHeaders.UserAgent.ParseAdd(CoreData.UserAgentString);
- HttpResponseMessage result = await client.GetAsync(DownloadUrl);
- using FileStream fs = new(InstallerPath, FileMode.CreateNew);
- await result.Content.CopyToAsync(fs);
- }
-
- string Hash = "";
- SHA256 Sha256 = SHA256.Create();
- using (FileStream stream = File.OpenRead(InstallerPath))
- {
- Hash = Convert.ToHexString(Sha256.ComputeHash(stream)).ToLower();
- }
-
- if (Hash == InstallerHash)
- {
-
- banner.Title = CoreTools.Translate("WingetUI {0} is ready to be installed.", LatestVersion.ToString(CultureInfo.InvariantCulture));
- banner.Message = CoreTools.Translate("The update will be installed upon closing WingetUI");
- banner.ActionButton = new Button
- {
- Content = CoreTools.Translate("Update now")
- };
- banner.ActionButton.Click += (_, _) => { MainWindow.HideWindow(); };
- banner.Severity = InfoBarSeverity.Success;
- banner.IsOpen = true;
- banner.IsClosable = true;
-
- if (MainWindow.Visible)
- {
- Logger.Debug("Waiting for mainWindow to be hidden");
- }
-
- while (MainWindow.Visible)
- {
- await Task.Delay(100);
- }
-
- if (Settings.Get("DisableAutoUpdateWingetUI"))
- {
- Logger.Warn("User disabled updates!");
- return;
- }
-
- Logger.ImportantInfo("The hash matches the expected value, starting update process...");
- Process p = new();
- p.StartInfo.FileName = "cmd.exe";
- p.StartInfo.Arguments = $"/c start /B \"\" \"{InstallerPath}\" /silent";
- p.StartInfo.UseShellExecute = true;
- p.StartInfo.CreateNoWindow = true;
- p.Start();
- DisposeAndQuit();
- }
- else
- {
- Logger.Error("Hash mismatch, not updating!");
- Logger.Error("Current hash : " + Hash);
- Logger.Error("Expected hash: " + InstallerHash);
- File.Delete(InstallerPath);
-
- banner.Title = CoreTools.Translate("The installer hash does not match the expected value.");
- banner.Message = CoreTools.Translate("The update will not continue.");
- banner.Severity = InfoBarSeverity.Error;
- banner.IsOpen = true;
- banner.IsClosable = true;
-
- await Task.Delay(3600000); // Check again in 1 hour
- UpdateUniGetUIIfPossible();
- }
- }
- else
- {
- Logger.Info("UniGetUI is up to date");
- await Task.Delay(3600000); // Check again in 1 hour
- UpdateUniGetUIIfPossible();
- }
- }
- catch (Exception e)
- {
- if (banner is not null)
- {
- banner.Title = CoreTools.Translate("An error occurred when checking for updates: ");
- banner.Message = e.Message;
- banner.Severity = InfoBarSeverity.Error;
- banner.IsOpen = true;
- banner.IsClosable = true;
- }
-
- Logger.Error(e);
-
- if (round >= 3)
- {
- return;
- }
-
- await Task.Delay(600000); // Try again in 10 minutes
- UpdateUniGetUIIfPossible(round + 1);
- }
- }
-
public void KillAndRestart()
{
Process.Start(CoreData.UniGetUIExecutableFile);
diff --git a/src/UniGetUI/AutoUpdater.cs b/src/UniGetUI/AutoUpdater.cs
new file mode 100644
index 0000000000..adec375c8f
--- /dev/null
+++ b/src/UniGetUI/AutoUpdater.cs
@@ -0,0 +1,352 @@
+using System.Diagnostics;
+using System.Globalization;
+using System.Net.NetworkInformation;
+using System.Security.Cryptography;
+using H.NotifyIcon;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.Windows.AppNotifications;
+using Microsoft.Windows.AppNotifications.Builder;
+using UniGetUI.Core.Data;
+using UniGetUI.Core.Logging;
+using UniGetUI.Core.SettingsEngine;
+using UniGetUI.Core.Tools;
+using UniGetUI.Interface;
+using UniGetUI.Interface.Enums;
+using Version = YamlDotNet.Core.Version;
+
+namespace UniGetUI;
+
+public class AutoUpdater
+{
+ public static Window Window = null!;
+ public static InfoBar Banner = null!;
+ //------------------------------------------------------------------------------------------------------------------
+ private const string STABLE_ENDPOINT = "https://www.marticliment.com/versions/unigetui/stable.ver";
+ private const string BETA_ENDPOINT = "https://www.marticliment.com/versions/unigetui/beta.ver";
+ private const string STABLE_INSTALLER_URL = "https://github.com/marticliment/UniGetUI/releases/latest/download/UniGetUI.Installer.exe";
+ private const string BETA_INSTALLER_URL = "https://github.com/marticliment/UniGetUI/releases/download/$TAG/UniGetUI.Installer.exe";
+ //------------------------------------------------------------------------------------------------------------------
+ public static bool ReleaseLockForAutoupdate_Notification;
+ public static bool ReleaseLockForAutoupdate_Window;
+ public static bool ReleaseLockForAutoupdate_UpdateBanner;
+ public static bool UpdateReadyToBeInstalled { get; private set; }
+
+ public static async Task UpdateCheckLoop(Window window, InfoBar banner)
+ {
+ if (Settings.Get("DisableAutoUpdateWingetUI"))
+ {
+ Logger.Warn("User has disabled updates");
+ return;
+ }
+
+ bool IsFirstLaunch = true;
+ Window = window;
+ Banner = banner;
+
+ await CoreTools.WaitForInternetConnection();
+ while (true)
+ {
+ // User could have disabled updates on runtime
+ if (Settings.Get("DisableAutoUpdateWingetUI"))
+ {
+ Logger.Warn("User has disabled updates");
+ return;
+ }
+ bool updateSucceeded = await CheckAndInstallUpdates(window, banner, false, IsFirstLaunch);
+ IsFirstLaunch = false;
+ await Task.Delay(TimeSpan.FromMinutes(updateSucceeded ? 60 : 10));
+ }
+ }
+
+ ///
+ /// Performs the entire update process, and returns true/false whether the process finished successfully;
+ ///
+ public static async Task CheckAndInstallUpdates(Window window, InfoBar banner, bool Verbose, bool AutoLaunch = false)
+ {
+ Window = window;
+ Banner = banner;
+ bool WasCheckingForUpdates = true;
+
+ try
+ {
+ if (Verbose) ShowMessage_ThreadSafe(
+ CoreTools.Translate("We are checking for updates."),
+ CoreTools.Translate("Please wait"),
+ InfoBarSeverity.Informational,
+ false
+ );
+
+ // Check for updates
+ string UpdatesEndpoint = Settings.Get("EnableUniGetUIBeta") ? BETA_ENDPOINT : STABLE_ENDPOINT;
+ string InstallerDownloadUrl = Settings.Get("EnableUniGetUIBeta") ? BETA_INSTALLER_URL : STABLE_INSTALLER_URL;
+ var (IsUpgradable, LatestVersion, InstallerHash) = await CheckForUpdates(UpdatesEndpoint);
+
+ if (IsUpgradable)
+ {
+ WasCheckingForUpdates = false;
+ InstallerDownloadUrl = InstallerDownloadUrl.Replace("$TAG", LatestVersion);
+
+ Logger.Info($"An update to UniGetUI version {LatestVersion} is available");
+ string InstallerPath = Path.Join(CoreData.UniGetUIDataDirectory, "UniGetUI Updater.exe");
+
+ if (File.Exists(InstallerPath)
+ && await CheckInstallerHash(InstallerPath, InstallerHash))
+ {
+ Logger.Info($"A cached valid installer was found, launching update process...");
+ return await PrepairToLaunchInstaller(InstallerPath, LatestVersion, AutoLaunch);
+ }
+ else
+ {
+ File.Delete(InstallerPath);
+ }
+
+ ShowMessage_ThreadSafe(
+ CoreTools.Translate("UniGetUI version {0} is being downloaded.", LatestVersion.ToString(CultureInfo.InvariantCulture)),
+ CoreTools.Translate("This may take a minute or two"),
+ InfoBarSeverity.Informational,
+ false);
+
+ // Download the installer
+ await DownloadInstaller(InstallerDownloadUrl, InstallerPath);
+
+ if (await CheckInstallerHash(InstallerPath, InstallerHash))
+ {
+ Logger.Info("The downloaded installer is valid, launching update process...");
+ return await PrepairToLaunchInstaller(InstallerPath, LatestVersion, AutoLaunch);
+ }
+ else
+ {
+ ShowMessage_ThreadSafe(
+ CoreTools.Translate("The installer authenticity could not be verified."),
+ CoreTools.Translate("The update process has been aborted."),
+ InfoBarSeverity.Error,
+ true);
+ return false;
+ }
+ }
+ else
+ {
+ if (Verbose) ShowMessage_ThreadSafe(
+ CoreTools.Translate("Great! You are on the latest version."),
+ CoreTools.Translate("There are no new UniGetUI versions to be installed"),
+ InfoBarSeverity.Success,
+ true
+ );
+ return true;
+ }
+
+ }
+ catch (Exception e)
+ {
+ Logger.Error("An error occurred while checking for updates: ");
+ Logger.Error(e);
+ // We don't want an error popping if updates can't
+ if(Verbose || !WasCheckingForUpdates) ShowMessage_ThreadSafe(
+ CoreTools.Translate("An error occurred when checking for updates: "),
+ e.Message,
+ InfoBarSeverity.Error,
+ true
+ );
+ return false;
+ }
+ }
+
+ ///
+ /// Checks whether new updates are available, and returns a tuple containing:
+ /// - A boolean that is set to True if new updates are available
+ /// - The new version name
+ /// - The hash of the installer for the new version, as a string.
+ ///
+ private static async Task<(bool, string, string)> CheckForUpdates(string endpoint)
+ {
+ Logger.Debug($"Begin check for updates on endpoint {endpoint}");
+ string[] UpdateResponse;
+ using (HttpClient client = new(CoreData.GenericHttpClientParameters))
+ {
+ client.Timeout = TimeSpan.FromSeconds(600);
+ client.DefaultRequestHeaders.UserAgent.ParseAdd(CoreData.UserAgentString);
+ UpdateResponse = (await client.GetStringAsync(endpoint)).Split("////");
+ }
+
+ if (UpdateResponse.Length >= 3)
+ {
+ int LatestVersion = int.Parse(UpdateResponse[0].Replace("\n", "").Replace("\r", "").Trim());
+ string InstallerHash = UpdateResponse[1].Replace("\n", "").Replace("\r", "").Trim();
+ string VersionName = UpdateResponse[2].Replace("\n", "").Replace("\r", "").Trim();
+ Logger.Debug($"Got response from endpoint: ({LatestVersion}, {VersionName}, {InstallerHash})");
+ return (LatestVersion > CoreData.BuildNumber, VersionName, InstallerHash);
+ }
+
+ Logger.Warn($"Received update string is {UpdateResponse[0]}");
+ throw new FormatException("The updates file does not follow the FloatVersion////Sha256Hash////VersionName format");
+ }
+
+ ///
+ /// Checks whether the downloaded updater matches the hash.
+ ///
+ private static async Task CheckInstallerHash(string installerLocation, string expectedHash)
+ {
+ Logger.Debug($"Checking updater hash on location {installerLocation}");
+ using (FileStream stream = File.OpenRead(installerLocation))
+ {
+ string hash = Convert.ToHexString(await SHA256.Create().ComputeHashAsync(stream)).ToLower();
+ if (hash == expectedHash.ToLower())
+ {
+ Logger.Debug($"The hashes match ({hash})");
+ return true;
+ }
+ Logger.Warn($"Hash mismatch.\nExpected: {expectedHash}\nGot: {hash}");
+ return false;
+ }
+ }
+
+ ///
+ /// Downloads the given installer to the given location
+ ///
+ private static async Task DownloadInstaller(string downloadUrl, string installerLocation)
+ {
+ Logger.Debug($"Downloading installer from {downloadUrl} to {installerLocation}");
+ using (HttpClient client = new(CoreData.GenericHttpClientParameters))
+ {
+ client.Timeout = TimeSpan.FromSeconds(600);
+ client.DefaultRequestHeaders.UserAgent.ParseAdd(CoreData.UserAgentString);
+ HttpResponseMessage result = await client.GetAsync(downloadUrl);
+ result.EnsureSuccessStatusCode();
+ using FileStream fs = new(installerLocation, FileMode.OpenOrCreate);
+ await result.Content.CopyToAsync(fs);
+ }
+ Logger.Debug("The download has finished successfully");
+ }
+
+ ///
+ /// Waits for the window to be closed if it is open and launches the updater
+ ///
+ private static async Task PrepairToLaunchInstaller(string installerLocation, string NewVersion, bool AutoLaunch)
+ {
+ Logger.Debug("Starting the process to launch the installer.");
+ UpdateReadyToBeInstalled = true;
+ ReleaseLockForAutoupdate_Window = false;
+ ReleaseLockForAutoupdate_Notification = false;
+ ReleaseLockForAutoupdate_UpdateBanner = false;
+
+ // Check if the user has disabled updates
+ if (Settings.Get("DisableAutoUpdateWingetUI"))
+ {
+ Banner.IsOpen = false;
+ Logger.Warn("User disabled updates!");
+ return true;
+ }
+
+ Window.DispatcherQueue.TryEnqueue(() =>
+ {
+ // Set the banner to Restart UniGetUI to update
+ var UpdateNowButton = new Button { Content = CoreTools.Translate("Update now") };
+ UpdateNowButton.Click += (_, _) => ReleaseLockForAutoupdate_UpdateBanner = true;
+ ShowMessage_ThreadSafe(
+ CoreTools.Translate("UniGetUI {0} is ready to be installed.", NewVersion),
+ CoreTools.Translate("The update process will start after closing UniGetUI"),
+ InfoBarSeverity.Success,
+ true,
+ UpdateNowButton);
+
+ // Show a toast notification
+ AppNotificationBuilder builder = new AppNotificationBuilder()
+ .SetScenario(AppNotificationScenario.Default)
+ .SetTag(CoreData.UniGetUICanBeUpdated.ToString())
+ .AddText(CoreTools.Translate("{0} can be updated to version {1}", "UniGetUI", NewVersion))
+ .SetAttributionText(CoreTools.Translate("You have currently version {0} installed", CoreData.VersionName))
+ .AddArgument("action", NotificationArguments.Show)
+ .AddButton(new AppNotificationButton(CoreTools.Translate("Update now"))
+ .AddArgument("action", NotificationArguments.ReleaseSelfUpdateLock)
+ );
+ AppNotification notification = builder.BuildNotification();
+ notification.ExpiresOnReboot = true;
+ AppNotificationManager.Default.Show(notification);
+
+ });
+
+ if (AutoLaunch && !Window.Visible)
+ {
+ Logger.Debug("AutoLaunch is enabled and the Window is hidden, launching installer...");
+ }
+ else
+ {
+ Logger.Debug("Waiting for mainWindow to be closed or for user to trigger the update from the notification...");
+ while (
+ !ReleaseLockForAutoupdate_Window &&
+ !ReleaseLockForAutoupdate_Notification &&
+ !ReleaseLockForAutoupdate_UpdateBanner)
+ {
+ await Task.Delay(100);
+ }
+ Logger.Debug("Autoupdater lock released, launching installer...");
+ }
+
+ if (Settings.Get("DisableAutoUpdateWingetUI"))
+ {
+ Logger.Warn("User has disabled updates");
+ return true;
+ }
+
+ await LaunchInstallerAndQuit(installerLocation);
+ return true;
+ }
+
+ ///
+ /// Launches the installer located on the installerLocation argument and quits UniGetUI
+ ///
+ private static async Task LaunchInstallerAndQuit(string installerLocation)
+ {
+ Logger.Debug("Launching the updater...");
+ Process p = new()
+ {
+ StartInfo = new()
+ {
+ FileName = installerLocation,
+ Arguments = "/SILENT /SUPPRESSMSGBOXES /NORESTART /SP-",
+ UseShellExecute = true,
+ CreateNoWindow = true,
+ }
+ };
+ p.Start();
+ ShowMessage_ThreadSafe(
+ CoreTools.Translate("UniGetUI is being updated..."),
+ CoreTools.Translate("This may take a minute or two"),
+ InfoBarSeverity.Informational,
+ false
+ );
+ await p.WaitForExitAsync();
+ ShowMessage_ThreadSafe(
+ CoreTools.Translate("Something went wrong while launching the updater."),
+ CoreTools.Translate("Please try again later"),
+ InfoBarSeverity.Error,
+ true
+ );
+ }
+
+ private static void ShowMessage_ThreadSafe(string Title, string Message, InfoBarSeverity MessageSeverity, bool BannerClosable, Button? ActionButton = null)
+ {
+ try
+ {
+ if (Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread() is null)
+ {
+ Window.DispatcherQueue.TryEnqueue(() =>
+ ShowMessage_ThreadSafe(Title, Message, MessageSeverity, BannerClosable, ActionButton));
+ return;
+ }
+
+ Banner.Title = Title;
+ Banner.Message = Message;
+ Banner.Severity = MessageSeverity;
+ Banner.IsClosable = BannerClosable;
+ Banner.ActionButton = ActionButton;
+ Banner.IsOpen = true;
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(ex);
+ }
+
+ }
+}
diff --git a/src/UniGetUI/Controls/SettingsWidgets/CheckboxButtonCard.cs b/src/UniGetUI/Controls/SettingsWidgets/CheckboxButtonCard.cs
new file mode 100644
index 0000000000..3fee7e56f7
--- /dev/null
+++ b/src/UniGetUI/Controls/SettingsWidgets/CheckboxButtonCard.cs
@@ -0,0 +1,87 @@
+using CommunityToolkit.WinUI.Controls;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using UniGetUI.Core.SettingsEngine;
+using UniGetUI.Core.Tools;
+
+// To learn more about WinUI, the WinUI project structure,
+// and more about our project templates, see: http://aka.ms/winui-project-info.
+
+namespace UniGetUI.Interface.Widgets
+{
+ public sealed class CheckboxButtonCard : SettingsCard
+ {
+ public CheckBox CheckBox;
+ public Button Button;
+ private bool IS_INVERTED;
+
+ private string setting_name = "";
+ public string SettingName
+ {
+ set {
+ setting_name = value;
+ IS_INVERTED = value.StartsWith("Disable");
+ CheckBox.IsChecked = Settings.Get(setting_name) ^ IS_INVERTED ^ ForceInversion;
+ Button.IsEnabled = (CheckBox.IsChecked ?? false) || _buttonAlwaysOn ;
+ }
+ }
+
+ public bool ForceInversion { get; set; }
+
+ public bool Checked
+ {
+ get => CheckBox.IsChecked ?? false;
+ }
+ public event EventHandler? StateChanged;
+ public new event EventHandler? Click;
+
+ public string CheckboxText
+ {
+ set => CheckBox.Content = CoreTools.Translate(value);
+ }
+
+ public string ButtonText
+ {
+ set => Button.Content = CoreTools.Translate(value);
+ }
+
+ private bool _buttonAlwaysOn;
+ public bool ButtonAlwaysOn
+ {
+ set => _buttonAlwaysOn = value;
+ }
+
+
+ public CheckboxButtonCard()
+ {
+ Button = new Button();
+ CheckBox = new CheckBox();
+ IS_INVERTED = false;
+
+ //ContentAlignment = ContentAlignment.Left;
+ //HorizontalAlignment = HorizontalAlignment.Stretch;
+
+ DefaultStyleKey = typeof(CheckboxCard);
+ Description = CheckBox;
+ Content = Button;
+ CheckBox.HorizontalAlignment = HorizontalAlignment.Stretch;
+ CheckBox.Checked += (_, _) =>
+ {
+ Settings.Set(setting_name, true ^ IS_INVERTED ^ ForceInversion);
+ StateChanged?.Invoke(this, EventArgs.Empty);
+ Button.IsEnabled = true;
+ };
+
+ CheckBox.Unchecked += (_, _) =>
+ {
+ Settings.Set(setting_name, false ^ IS_INVERTED ^ ForceInversion);
+ StateChanged?.Invoke(this, EventArgs.Empty);
+ Button.IsEnabled = _buttonAlwaysOn;
+ };
+
+
+ Button.MinWidth = 200;
+ Button.Click += (s, e) => Click?.Invoke(s, e);
+ }
+ }
+}
diff --git a/src/UniGetUI/EntryPoint.cs b/src/UniGetUI/EntryPoint.cs
index 8a96f4ee86..71702ada15 100644
--- a/src/UniGetUI/EntryPoint.cs
+++ b/src/UniGetUI/EntryPoint.cs
@@ -55,7 +55,7 @@ Welcome to UniGetUI Version {CoreData.VersionName}
Logger.ImportantInfo(textart);
Logger.ImportantInfo(" ");
- Logger.ImportantInfo($"Version Code: {CoreData.VersionNumber}");
+ Logger.ImportantInfo($"Build {CoreData.BuildNumber}");
Logger.ImportantInfo($"Encoding Code Page set to {CoreData.CODE_PAGE}");
// WinRT single-instance fancy stuff
diff --git a/src/UniGetUI/MainWindow.xaml.cs b/src/UniGetUI/MainWindow.xaml.cs
index 6583710772..27a6c0e277 100644
--- a/src/UniGetUI/MainWindow.xaml.cs
+++ b/src/UniGetUI/MainWindow.xaml.cs
@@ -110,6 +110,8 @@ public MainWindow()
{
ParametersToProcess.Enqueue(arg);
}
+
+ _ = AutoUpdater.UpdateCheckLoop(this, UpdatesBanner);
}
public void HandleNotificationActivation(AppNotificationActivatedEventArgs args)
@@ -130,10 +132,14 @@ public void HandleNotificationActivation(AppNotificationActivatedEventArgs args)
{
Activate();
}
+ else if (action == NotificationArguments.ReleaseSelfUpdateLock)
+ {
+ AutoUpdater.ReleaseLockForAutoupdate_Notification = true;
+ }
else
{
throw new ArgumentException(
- "args.Argument was not set to a value present in Enums.NotificationArguments");
+ $"args.Argument was not set to a value present in Enums.NotificationArguments (value is {action})");
}
Logger.Debug("Notification activated: " + args.Arguments);
@@ -144,8 +150,9 @@ public void HandleNotificationActivation(AppNotificationActivatedEventArgs args)
///
public async void HandleClosingEvent(AppWindow sender, AppWindowClosingEventArgs args)
{
+ AutoUpdater.ReleaseLockForAutoupdate_Window = true;
SaveGeometry(Force: true);
- if (!Settings.Get("DisableSystemTray"))
+ if (!Settings.Get("DisableSystemTray") || AutoUpdater.UpdateReadyToBeInstalled)
{
args.Cancel = true;
try
diff --git a/src/UniGetUI/Pages/SettingsPage.xaml b/src/UniGetUI/Pages/SettingsPage.xaml
index a4a21f1150..1ca315a095 100644
--- a/src/UniGetUI/Pages/SettingsPage.xaml
+++ b/src/UniGetUI/Pages/SettingsPage.xaml
@@ -75,9 +75,15 @@
Click="OpenWelcomeWizard"
IsEnabled="False"
/-->
-
+
ProjectDebugger
- ModernWindow (Unpackaged)
+ UniGetUI (Unpackaged)
diff --git a/src/UniGetUI/app.manifest b/src/UniGetUI/app.manifest
index c899a29bb2..70ca760360 100644
--- a/src/UniGetUI/app.manifest
+++ b/src/UniGetUI/app.manifest
@@ -2,7 +2,7 @@