From b54c248d0e65eafaa1f7c493d03940c881115f31 Mon Sep 17 00:00:00 2001 From: Paul Hebble Date: Tue, 13 Feb 2024 23:53:22 -0600 Subject: [PATCH] Prompt for client upgrade when newer spec is found --- Core/Meta.cs | 15 +++-------- Core/Repositories/RepositoryData.cs | 31 +++++++++++++++------- Core/Repositories/RepositoryDataManager.cs | 5 +++- Core/Types/CkanModule.cs | 7 +---- Core/Types/ModuleInstallDescriptor.cs | 2 +- Core/VersionFormat.cs | 1 - GUI/Dialogs/NewUpdateDialog.Designer.cs | 2 +- GUI/Dialogs/NewUpdateDialog.resx | 1 - GUI/Dialogs/SettingsDialog.cs | 2 +- GUI/Main/Main.cs | 7 ++++- GUI/Main/MainAutoUpdate.cs | 12 ++++----- GUI/Main/MainRepo.cs | 16 +++++++++++ GUI/Properties/Resources.resx | 1 + GlobalAssemblyInfo.cs | 2 +- 14 files changed, 63 insertions(+), 41 deletions(-) diff --git a/Core/Meta.cs b/Core/Meta.cs index 599302f28b..ef15043acd 100644 --- a/Core/Meta.cs +++ b/Core/Meta.cs @@ -2,6 +2,8 @@ using System.Linq; using System.Reflection; +using CKAN.Versioning; + namespace CKAN { public static class Meta @@ -16,6 +18,8 @@ public static string GetProductName() .GetAssemblyAttribute() .Product; + public static readonly ModuleVersion ReleaseVersion = new ModuleVersion(GetVersion()); + public static string GetVersion(VersionFormat format = VersionFormat.Normal) { var version = Assembly @@ -25,8 +29,6 @@ public static string GetVersion(VersionFormat format = VersionFormat.Normal) switch (format) { - case VersionFormat.Short: - return $"v{version.UpToCharacters(shortDelimiters)}"; case VersionFormat.Normal: return "v" + Assembly.GetExecutingAssembly() .GetAssemblyAttribute() @@ -38,15 +40,6 @@ public static string GetVersion(VersionFormat format = VersionFormat.Normal) } } - private static readonly char[] shortDelimiters = new char[] { '-', '+' }; - - private static string UpToCharacters(this string orig, char[] what) - => orig.UpToIndex(orig.IndexOfAny(what)); - - private static string UpToIndex(this string orig, int index) - => index == -1 ? orig - : orig.Substring(0, index); - private static T GetAssemblyAttribute(this Assembly assembly) => (T)assembly.GetCustomAttributes(typeof(T), false) .First(); diff --git a/Core/Repositories/RepositoryData.cs b/Core/Repositories/RepositoryData.cs index 1829e06c89..65f493a679 100644 --- a/Core/Repositories/RepositoryData.cs +++ b/Core/Repositories/RepositoryData.cs @@ -29,7 +29,8 @@ namespace CKAN using ArchiveList = Tuple, SortedDictionary, GameVersion[], - Repository[]>; + Repository[], + bool>; /// /// Represents everything we retrieve from one metadata repository @@ -63,6 +64,12 @@ public class RepositoryData [JsonProperty("repositories", NullValueHandling = NullValueHandling.Ignore)] public readonly Repository[] Repositories; + /// + /// true if any module we found requires a newer client version, false otherwise + /// + [JsonIgnore] + public readonly bool UnsupportedSpec; + private RepositoryData(Dictionary modules, SortedDictionary counts, GameVersion[] versions, @@ -84,7 +91,8 @@ private RepositoryData(Dictionary modules, public RepositoryData(IEnumerable modules, SortedDictionary counts, IEnumerable versions, - IEnumerable repos) + IEnumerable repos, + bool unsupportedSpec) : this(modules?.GroupBy(m => m.identifier) .ToDictionary(grp => grp.Key, grp => new AvailableModule(grp.Key, grp)), @@ -92,6 +100,7 @@ public RepositoryData(IEnumerable modules, (versions ?? Enumerable.Empty()).ToArray(), (repos ?? Enumerable.Empty()).ToArray()) { + UnsupportedSpec = unsupportedSpec; } [JsonConstructor] @@ -192,8 +201,9 @@ private static RepositoryData FromTarGz(string path, IGame game, IProgress (List modules, SortedDictionary counts, GameVersion[] versions, - Repository[] repos) = AggregateArchiveEntries(archiveEntriesFromTar(tarStream, game)); - return new RepositoryData(modules, counts, versions, repos); + Repository[] repos, + bool unsupSpec) = AggregateArchiveEntries(archiveEntriesFromTar(tarStream, game)); + return new RepositoryData(modules, counts, versions, repos, unsupSpec); } } @@ -249,9 +259,10 @@ private static RepositoryData FromZip(string path, IGame game, IProgress p (List modules, SortedDictionary counts, GameVersion[] versions, - Repository[] repos) = AggregateArchiveEntries(archiveEntriesFromZip(zipfile, game)); + Repository[] repos, + bool unsupSpec) = AggregateArchiveEntries(archiveEntriesFromZip(zipfile, game)); zipfile.Close(); - return new RepositoryData(modules, counts, versions, repos); + return new RepositoryData(modules, counts, versions, repos, unsupSpec); } } @@ -266,7 +277,7 @@ private static ParallelQuery archiveEntriesFromZip(ZipFile zipfile entry.Offset)); private static ArchiveList AggregateArchiveEntries(ParallelQuery entries) - => entries.Aggregate(new ArchiveList(new List(), null, null, null), + => entries.Aggregate(new ArchiveList(new List(), null, null, null, false), (subtotal, item) => item == null ? subtotal @@ -276,12 +287,14 @@ private static ArchiveList AggregateArchiveEntries(ParallelQuery e : subtotal.Item1.Append(item.Item1).ToList(), subtotal.Item2 ?? item.Item2, subtotal.Item3 ?? item.Item3, - subtotal.Item4 ?? item.Item4), + subtotal.Item4 ?? item.Item4, + subtotal.Item5 || item.Item1 == null), (total, subtotal) => new ArchiveList(total.Item1.Concat(subtotal.Item1).ToList(), total.Item2 ?? subtotal.Item2, total.Item3 ?? subtotal.Item3, - total.Item4 ?? subtotal.Item4), + total.Item4 ?? subtotal.Item4, + total.Item5 || subtotal.Item5), total => total); private static ArchiveEntry getArchiveEntry(string filename, diff --git a/Core/Repositories/RepositoryDataManager.cs b/Core/Repositories/RepositoryDataManager.cs index 3cf20d9c35..6d403f9e02 100644 --- a/Core/Repositories/RepositoryDataManager.cs +++ b/Core/Repositories/RepositoryDataManager.cs @@ -116,6 +116,7 @@ public enum UpdateResult Failed, Updated, NoChanges, + OutdatedClient, } /// @@ -218,7 +219,9 @@ public UpdateResult Update(Repository[] repos, downloader.onOneCompleted -= setETag; } - return UpdateResult.Updated; + return repositoriesData.Values.Any(repoData => repoData.UnsupportedSpec) + ? UpdateResult.OutdatedClient + : UpdateResult.Updated; } /// diff --git a/Core/Types/CkanModule.cs b/Core/Types/CkanModule.cs index f40d36b674..b0211c3cd6 100644 --- a/Core/Types/CkanModule.cs +++ b/Core/Types/CkanModule.cs @@ -700,12 +700,7 @@ bool IEquatable.Equals(CkanModule other) /// Returns true if we support at least spec_version of the CKAN spec. /// internal static bool IsSpecSupported(ModuleVersion spec_version) - { - // This could be a read-only state variable; do we have those in C#? - ModuleVersion release = new ModuleVersion(Meta.GetVersion(VersionFormat.Short)); - - return release == null || release.IsGreaterThan(spec_version); - } + => Meta.ReleaseVersion.IsGreaterThan(spec_version); /// /// Returns true if we support the CKAN spec used by this module. diff --git a/Core/Types/ModuleInstallDescriptor.cs b/Core/Types/ModuleInstallDescriptor.cs index b34b9411a4..6869086499 100644 --- a/Core/Types/ModuleInstallDescriptor.cs +++ b/Core/Types/ModuleInstallDescriptor.cs @@ -293,7 +293,7 @@ private void EnsurePattern() } else { - throw new UnsupportedKraken(Properties.Resources.ModuleInstallDescriptorRequireFileFind); + throw new Kraken(Properties.Resources.ModuleInstallDescriptorRequireFileFind); } } } diff --git a/Core/VersionFormat.cs b/Core/VersionFormat.cs index 83e046623f..73806c6346 100644 --- a/Core/VersionFormat.cs +++ b/Core/VersionFormat.cs @@ -2,7 +2,6 @@ namespace CKAN { public enum VersionFormat { - Short, Normal, Full } diff --git a/GUI/Dialogs/NewUpdateDialog.Designer.cs b/GUI/Dialogs/NewUpdateDialog.Designer.cs index 44b50fa2d0..fe9f3bb9f9 100644 --- a/GUI/Dialogs/NewUpdateDialog.Designer.cs +++ b/GUI/Dialogs/NewUpdateDialog.Designer.cs @@ -99,7 +99,7 @@ private void InitializeComponent() // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.ClientSize = new System.Drawing.Size(426, 310); this.Controls.Add(this.CancelUpdateButton); this.Controls.Add(this.InstallUpdateButton); diff --git a/GUI/Dialogs/NewUpdateDialog.resx b/GUI/Dialogs/NewUpdateDialog.resx index 5e1709e78e..e5e0d6eb8f 100644 --- a/GUI/Dialogs/NewUpdateDialog.resx +++ b/GUI/Dialogs/NewUpdateDialog.resx @@ -118,7 +118,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Version: - v0.0.0 Install Not now A new version of CKAN is available diff --git a/GUI/Dialogs/SettingsDialog.cs b/GUI/Dialogs/SettingsDialog.cs index 511027bcdb..fb78d80d8b 100644 --- a/GUI/Dialogs/SettingsDialog.cs +++ b/GUI/Dialogs/SettingsDialog.cs @@ -585,7 +585,7 @@ private void CheckForUpdatesButton_Click(object sender, EventArgs e) private void InstallUpdateButton_Click(object sender, EventArgs e) { - if (AutoUpdate.CanUpdate) + if (Main.Instance.CheckForCKANUpdate()) { Hide(); Main.Instance.UpdateCKAN(); diff --git a/GUI/Main/Main.cs b/GUI/Main/Main.cs index b46430682f..1455ec0a34 100644 --- a/GUI/Main/Main.cs +++ b/GUI/Main/Main.cs @@ -419,7 +419,12 @@ private void CurrentInstanceUpdated() .Resolve(), configuration); - bool autoUpdating = CheckForCKANUpdate(); + bool autoUpdating = configuration.CheckForUpdatesOnLaunch + && CheckForCKANUpdate(); + if (autoUpdating) + { + UpdateCKAN(); + } var pluginsPath = Path.Combine(CurrentInstance.CkanDir(), "Plugins"); if (!Directory.Exists(pluginsPath)) diff --git a/GUI/Main/MainAutoUpdate.cs b/GUI/Main/MainAutoUpdate.cs index 8a969dcc22..13d1885e4e 100644 --- a/GUI/Main/MainAutoUpdate.cs +++ b/GUI/Main/MainAutoUpdate.cs @@ -42,9 +42,9 @@ private void AutoUpdatePrompts(IConfiguration coreConfig, /// /// true if update found, false otherwise. /// - private bool CheckForCKANUpdate() + public bool CheckForCKANUpdate() { - if (configuration.CheckForUpdatesOnLaunch && AutoUpdate.CanUpdate) + if (AutoUpdate.CanUpdate) { try { @@ -52,16 +52,14 @@ private bool CheckForCKANUpdate() var mainConfig = ServiceLocator.Container.Resolve(); var update = updater.GetUpdate(mainConfig.DevBuilds ?? false); var latestVersion = update.Version; - var currentVersion = new ModuleVersion(Meta.GetVersion()); - if (latestVersion.IsGreaterThan(currentVersion)) + if (latestVersion.IsGreaterThan(Meta.ReleaseVersion)) { - log.Debug("Found higher ckan version"); + log.DebugFormat("Found higher CKAN version: {0}", latestVersion); var releaseNotes = update.ReleaseNotes; var dialog = new NewUpdateDialog(latestVersion.ToString(), releaseNotes); if (dialog.ShowDialog(this) == DialogResult.OK) { - UpdateCKAN(); return true; } } @@ -90,7 +88,7 @@ public void UpdateCKAN() Wait.SetDescription(string.Format(Properties.Resources.MainUpgradingTo, update.Version)); - log.Info("Start ckan update"); + log.Info("Starting CKAN update"); Wait.StartWaiting((sender, args) => updater.StartUpdateProcess(true, mainConfig.DevBuilds ?? false, currentUser), UpdateReady, false, diff --git a/GUI/Main/MainRepo.cs b/GUI/Main/MainRepo.cs index ffa1c9403e..a2e160053d 100644 --- a/GUI/Main/MainRepo.cs +++ b/GUI/Main/MainRepo.cs @@ -248,6 +248,22 @@ private void PostUpdateRepo(object sender, RunWorkerCompletedEventArgs e) } break; + + case RepositoryDataManager.UpdateResult.OutdatedClient: + currentUser.RaiseMessage(Properties.Resources.MainRepoOutdatedClient); + if (CheckForCKANUpdate()) + { + UpdateCKAN(); + } + else + { + // No update available or user said no. Proceed as normal. + ShowRefreshQuestion(); + UpgradeNotification(); + RefreshModList(false, oldModules); + } + break; + case RepositoryDataManager.UpdateResult.Updated: default: currentUser.RaiseMessage(Properties.Resources.MainRepoSuccess); diff --git a/GUI/Properties/Resources.resx b/GUI/Properties/Resources.resx index 869ca7a985..bfdf7bdb44 100644 --- a/GUI/Properties/Resources.resx +++ b/GUI/Properties/Resources.resx @@ -323,6 +323,7 @@ If you suspect a bug in the client: https://github.com/KSP-CKAN/CKAN/issues/new/ Repositories already up to date. Repository update failed! Repositories successfully updated. + Repositories updated, but found modules that require a new version of CKAN. Checking for updates... Would you like CKAN to refresh the modlist every time it is loaded? (You can always manually refresh using the button up top.) {0} update(s) available Click to upgrade diff --git a/GlobalAssemblyInfo.cs b/GlobalAssemblyInfo.cs index a8c78a3898..8315b2c45a 100644 --- a/GlobalAssemblyInfo.cs +++ b/GlobalAssemblyInfo.cs @@ -2,7 +2,7 @@ [assembly: AssemblyProduct("CKAN")] [assembly: AssemblyCompany("CKAN Contributors")] -[assembly: AssemblyCopyright("Copyright © 2014–2023")] +[assembly: AssemblyCopyright("Copyright © 2014–2024")] #if DEBUG [assembly: AssemblyConfiguration("Debug")]