From 1794ae9ddd569e1ea64bb6f0add080fb19524ff7 Mon Sep 17 00:00:00 2001 From: Paul Hebble Date: Thu, 15 Aug 2024 15:18:38 -0500 Subject: [PATCH] Ability to update repos without a game instance --- Cmdline/Action/Update.cs | 154 ++++++++++++++++----- Cmdline/Main.cs | 4 +- Cmdline/Properties/Resources.resx | 6 +- Core/Relationships/RelationshipResolver.cs | 2 +- Core/Repositories/Repository.cs | 6 + Core/Repositories/RepositoryDataManager.cs | 3 +- 6 files changed, 132 insertions(+), 43 deletions(-) diff --git a/Cmdline/Action/Update.cs b/Cmdline/Action/Update.cs index dbb71cfdd9..5f59a1c493 100644 --- a/Cmdline/Action/Update.cs +++ b/Cmdline/Action/Update.cs @@ -1,21 +1,28 @@ +using System; +using System.IO; using System.Collections.Generic; using System.Linq; using CommandLine; +using CKAN.Versioning; +using CKAN.Games; + namespace CKAN.CmdLine { - public class Update : ICommand + // Does not always need an instance, so this is not an ICommand + public class Update { /// /// Initialize the update command object /// /// GameInstanceManager containing our instances /// IUser object for interaction - public Update(RepositoryDataManager repoData, IUser user) + public Update(RepositoryDataManager repoData, IUser user, GameInstanceManager manager) { this.repoData = repoData; this.user = user; + this.manager = manager; } /// @@ -26,22 +33,66 @@ public Update(RepositoryDataManager repoData, IUser user) /// /// Exit code for shell environment /// - public int RunCommand(CKAN.GameInstance instance, object raw_options) + public int RunCommand(object raw_options) { UpdateOptions options = (UpdateOptions) raw_options; - List compatible_prior = null; - - if (options.list_changes) - { - // Get a list of compatible modules prior to the update. - var registry = RegistryManager.Instance(instance, repoData).registry; - compatible_prior = registry.CompatibleModules(instance.VersionCriteria()).ToList(); - } - try { - UpdateRepository(instance); + if (options.repositoryURLs != null || options.game != null) + { + var game = options.game == null ? KnownGames.knownGames.First() + : KnownGames.GameByShortName(options.game); + if (game == null) + { + user.RaiseError(Properties.Resources.UpdateBadGame, + options.game, + string.Join(", ", KnownGames.AllGameShortNames())); + return Exit.BADOPT; + } + var repos = (options.repositoryURLs ?? Enumerable.Empty()) + .Select((url, i) => + new Repository(string.Format(Properties.Resources.UpdateURLRepoName, + i + 1), + getUri(url))) + .DefaultIfEmpty(Repository.DefaultGameRepo(game)) + .ToArray(); + List availablePrior = null; + if (options.list_changes) + { + availablePrior = repoData.GetAllAvailableModules(repos) + .Select(am => am.Latest()) + .ToList(); + } + UpdateRepositories(game, repos, options.force); + if (options.list_changes) + { + PrintChanges(availablePrior, + repoData.GetAllAvailableModules(repos) + .Select(am => am.Latest()) + .ToList()); + } + } + else + { + var instance = MainClass.GetGameInstance(manager); + Registry registry = null; + List compatible_prior = null; + GameVersionCriteria crit = null; + if (options.list_changes) + { + // Get a list of compatible modules prior to the update. + registry = RegistryManager.Instance(instance, repoData).registry; + crit = instance.VersionCriteria(); + compatible_prior = registry.CompatibleModules(crit).ToList(); + } + UpdateRepositories(instance, options.force); + if (options.list_changes) + { + PrintChanges(compatible_prior, + registry.CompatibleModules(crit).ToList()); + } + } } catch (MissingCertificateKraken kraken) { @@ -50,12 +101,6 @@ public int RunCommand(CKAN.GameInstance instance, object raw_options) return Exit.ERROR; } - if (options.list_changes) - { - var registry = RegistryManager.Instance(instance, repoData).registry; - PrintChanges(compatible_prior, registry.CompatibleModules(instance.VersionCriteria()).ToList()); - } - return Exit.OK; } @@ -64,22 +109,25 @@ public int RunCommand(CKAN.GameInstance instance, object raw_options) /// /// List of the compatible modules prior to the update. /// List of the compatible modules after the update. - private void PrintChanges(List modules_prior, List modules_post) + private void PrintChanges(List modules_prior, + List modules_post) { var prior = new HashSet(modules_prior, new NameComparer()); - var post = new HashSet(modules_post, new NameComparer()); - + var post = new HashSet(modules_post, new NameComparer()); - var added = new HashSet(post.Except(prior, new NameComparer())); + var added = new HashSet(post.Except(prior, new NameComparer())); var removed = new HashSet(prior.Except(post, new NameComparer())); - // Default compare includes versions var unchanged = post.Intersect(prior); - var updated = post.Except(unchanged).Except(added).Except(removed).ToList(); + var updated = post.Except(unchanged) + .Except(added) + .Except(removed) + .ToList(); // Print the changes. - user.RaiseMessage(Properties.Resources.UpdateChangesSummary, added.Count(), removed.Count(), updated.Count()); + user.RaiseMessage(Properties.Resources.UpdateChangesSummary, + added.Count(), removed.Count(), updated.Count()); if (added.Count > 0) { @@ -126,28 +174,64 @@ private void PrintModules(string message, IEnumerable modules) } /// - /// Updates the repository. + /// Updates repositories for a game instance /// - /// The KSP instance to work on. + /// The game instance to work on. /// Repository to update. If null all repositories are used. - private void UpdateRepository(CKAN.GameInstance instance) + private void UpdateRepositories(CKAN.GameInstance instance, bool force = false) { - RegistryManager registry_manager = RegistryManager.Instance(instance, repoData); - - repoData.Update(registry_manager.registry.Repositories.Values.ToArray(), - instance.game, false, new NetAsyncDownloader(user), user); + var registry = RegistryManager.Instance(instance, repoData).registry; + var result = repoData.Update(registry.Repositories.Values.ToArray(), + instance.game, force, + new NetAsyncDownloader(user), user); + if (result == RepositoryDataManager.UpdateResult.Updated) + { + user.RaiseMessage(Properties.Resources.UpdateSummary, + registry.CompatibleModules(instance.VersionCriteria()).Count()); + } + } - user.RaiseMessage(Properties.Resources.UpdateSummary, registry_manager.registry.CompatibleModules(instance.VersionCriteria()).Count()); + /// + /// Updates repositories + /// + /// The game for which the URLs contain metadata + /// Repositories to update + private void UpdateRepositories(IGame game, Repository[] repos, bool force = false) + { + var result = repoData.Update(repos, game, force, new NetAsyncDownloader(user), user); + if (result == RepositoryDataManager.UpdateResult.Updated) + { + user.RaiseMessage(Properties.Resources.UpdateSummary, + repoData.GetAllAvailableModules(repos).Count()); + } } + private Uri getUri(string arg) + => Uri.IsWellFormedUriString(arg, UriKind.Absolute) + ? new Uri(arg) + : File.Exists(arg) + ? new Uri(Path.GetFullPath(arg)) + : throw new FileNotFoundKraken(arg); + private readonly RepositoryDataManager repoData; private readonly IUser user; + private readonly GameInstanceManager manager; } internal class UpdateOptions : InstanceSpecificOptions { - [Option("list-changes", DefaultValue = false, HelpText = "List new and removed modules")] + [Option('l', "list-changes", DefaultValue = false, HelpText = "List new and removed modules")] public bool list_changes { get; set; } + + // Can't specify DefaultValue here because we want to fall back to instance-based updates when omitted + [Option('g', "game", HelpText = "Game for which to update repositories")] + public string game { set; get; } + + [OptionArray('u', "urls", HelpText = "URLs of repositories to update")] + public string[] repositoryURLs { get; set; } + + [Option('f', "force", DefaultValue = false, HelpText = "Download and parse metadata even if it hasn't changed")] + public bool force { get; set; } } } diff --git a/Cmdline/Main.cs b/Cmdline/Main.cs index 63a7435d37..7a7bd36c23 100644 --- a/Cmdline/Main.cs +++ b/Cmdline/Main.cs @@ -217,12 +217,11 @@ private static int RunSimpleAction(Options cmdline, CommonOptions options, strin return Version(user); case "update": - return (new Update(repoData, user)).RunCommand(GetGameInstance(manager), cmdline.options); + return (new Update(repoData, user, manager)).RunCommand(cmdline.options); case "available": return (new Available(repoData, user)).RunCommand(GetGameInstance(manager), cmdline.options); - case "add": case "install": Scan(GetGameInstance(manager), user, cmdline.action); return (new Install(manager, repoData, user)).RunCommand(GetGameInstance(manager), cmdline.options); @@ -247,7 +246,6 @@ private static int RunSimpleAction(Options cmdline, CommonOptions options, strin case "search": return (new Search(repoData, user)).RunCommand(GetGameInstance(manager), options); - case "uninstall": case "remove": return (new Remove(manager, repoData, user)).RunCommand(GetGameInstance(manager), cmdline.options); diff --git a/Cmdline/Properties/Resources.resx b/Cmdline/Properties/Resources.resx index 0abd3627c8..27d9a204b3 100644 --- a/Cmdline/Properties/Resources.resx +++ b/Cmdline/Properties/Resources.resx @@ -359,11 +359,13 @@ Try `ckan list` for a list of installed mods. Filename: {0} Version Game Versions - Found {0} new modules, {1} removed modules and {2} updated modules + Game {0} not found! Valid options are: {1} + URL #{0} + Found {0} new modules, {1} removed modules, and {2} updated modules. New modules [Name (CKAN identifier)]: Removed modules [Name (CKAN identifier)]: Updated modules [Name (CKAN identifier)]: - Updated information on {0} compatible modules + Updated information on {0} modules. The `--dev-build` and `--stable-release` flags cannot be combined! Switching to dev builds Switching to stable releases diff --git a/Core/Relationships/RelationshipResolver.cs b/Core/Relationships/RelationshipResolver.cs index 7a70154a2f..5712baa9d4 100644 --- a/Core/Relationships/RelationshipResolver.cs +++ b/Core/Relationships/RelationshipResolver.cs @@ -694,7 +694,7 @@ private void AddReason(CkanModule module, SelectionReason reason) /// Depends relationships with suppress_recommendations=true, /// to be applied to all recommendations and suggestions /// - private HashSet suppressedRecommenders = new HashSet(); + private readonly HashSet suppressedRecommenders = new HashSet(); private readonly IRegistryQuerier registry; private readonly GameVersionCriteria versionCrit; diff --git a/Core/Repositories/Repository.cs b/Core/Repositories/Repository.cs index 6e449599b2..514acdeef7 100644 --- a/Core/Repositories/Repository.cs +++ b/Core/Repositories/Repository.cs @@ -3,6 +3,8 @@ using Newtonsoft.Json; +using CKAN.Games; + namespace CKAN { public class Repository : IEquatable @@ -53,5 +55,9 @@ public override int GetHashCode() public override string ToString() => string.Format("{0} ({1}, {2})", name, priority, uri); + + public static Repository DefaultGameRepo(IGame game) + => new Repository($"{game.ShortName}-{default_ckan_repo_name}", + game.DefaultRepositoryURL); } } diff --git a/Core/Repositories/RepositoryDataManager.cs b/Core/Repositories/RepositoryDataManager.cs index e4d14be9b8..a698ba0daf 100644 --- a/Core/Repositories/RepositoryDataManager.cs +++ b/Core/Repositories/RepositoryDataManager.cs @@ -151,8 +151,7 @@ public UpdateResult Update(Repository[] repos, // Check if any ETags have changed, quit if not user.RaiseProgress(Properties.Resources.NetRepoCheckingForUpdates, 0); - var toUpdate = repos.DefaultIfEmpty(new Repository($"{game.ShortName}-{Repository.default_ckan_repo_name}", - game.DefaultRepositoryURL)) + var toUpdate = repos.DefaultIfEmpty(Repository.DefaultGameRepo(game)) .DistinctBy(r => r.uri) .Where(r => r.uri != null && (r.uri.IsFile