Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to update repos without a game instance #4161

Merged
merged 1 commit into from
Aug 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 119 additions & 35 deletions Cmdline/Action/Update.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Initialize the update command object
/// </summary>
/// <param name="mgr">GameInstanceManager containing our instances</param>
/// <param name="user">IUser object for interaction</param>
public Update(RepositoryDataManager repoData, IUser user)
public Update(RepositoryDataManager repoData, IUser user, GameInstanceManager manager)
{
this.repoData = repoData;
this.user = user;
this.manager = manager;
}

/// <summary>
Expand All @@ -26,22 +33,66 @@ public Update(RepositoryDataManager repoData, IUser user)
/// <returns>
/// Exit code for shell environment
/// </returns>
public int RunCommand(CKAN.GameInstance instance, object raw_options)
public int RunCommand(object raw_options)
{
UpdateOptions options = (UpdateOptions) raw_options;

List<CkanModule> 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<string>())
.Select((url, i) =>
new Repository(string.Format(Properties.Resources.UpdateURLRepoName,
i + 1),
getUri(url)))
.DefaultIfEmpty(Repository.DefaultGameRepo(game))
.ToArray();
List<CkanModule> 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<CkanModule> 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)
{
Expand All @@ -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;
}

Expand All @@ -64,22 +109,25 @@ public int RunCommand(CKAN.GameInstance instance, object raw_options)
/// </summary>
/// <param name="modules_prior">List of the compatible modules prior to the update.</param>
/// <param name="modules_post">List of the compatible modules after the update.</param>
private void PrintChanges(List<CkanModule> modules_prior, List<CkanModule> modules_post)
private void PrintChanges(List<CkanModule> modules_prior,
List<CkanModule> modules_post)
{
var prior = new HashSet<CkanModule>(modules_prior, new NameComparer());
var post = new HashSet<CkanModule>(modules_post, new NameComparer());

var post = new HashSet<CkanModule>(modules_post, new NameComparer());

var added = new HashSet<CkanModule>(post.Except(prior, new NameComparer()));
var added = new HashSet<CkanModule>(post.Except(prior, new NameComparer()));
var removed = new HashSet<CkanModule>(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)
{
Expand Down Expand Up @@ -126,28 +174,64 @@ private void PrintModules(string message, IEnumerable<CkanModule> modules)
}

/// <summary>
/// Updates the repository.
/// Updates repositories for a game instance
/// </summary>
/// <param name="instance">The KSP instance to work on.</param>
/// <param name="instance">The game instance to work on.</param>
/// <param name="repository">Repository to update. If null all repositories are used.</param>
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());
/// <summary>
/// Updates repositories
/// </summary>
/// <param name="game">The game for which the URLs contain metadata</param>
/// <param name="repos">Repositories to update</param>
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; }
}

}
4 changes: 1 addition & 3 deletions Cmdline/Main.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);

Expand Down
6 changes: 4 additions & 2 deletions Cmdline/Properties/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -359,11 +359,13 @@ Try `ckan list` for a list of installed mods.</value></data>
<data name="ShowFileName" xml:space="preserve"><value>Filename: {0}</value></data>
<data name="ShowVersionHeader" xml:space="preserve"><value>Version</value></data>
<data name="ShowGameVersionsHeader" xml:space="preserve"><value>Game Versions</value></data>
<data name="UpdateChangesSummary" xml:space="preserve"><value>Found {0} new modules, {1} removed modules and {2} updated modules</value></data>
<data name="UpdateBadGame" xml:space="preserve"><value>Game {0} not found! Valid options are: {1}</value></data>
<data name="UpdateURLRepoName" xml:space="preserve"><value>URL #{0}</value></data>
<data name="UpdateChangesSummary" xml:space="preserve"><value>Found {0} new modules, {1} removed modules, and {2} updated modules.</value></data>
<data name="UpdateAddedHeader" xml:space="preserve"><value>New modules [Name (CKAN identifier)]:</value></data>
<data name="UpdateRemovedHeader" xml:space="preserve"><value>Removed modules [Name (CKAN identifier)]:</value></data>
<data name="UpdateUpdatedHeader" xml:space="preserve"><value>Updated modules [Name (CKAN identifier)]:</value></data>
<data name="UpdateSummary" xml:space="preserve"><value>Updated information on {0} compatible modules</value></data>
<data name="UpdateSummary" xml:space="preserve"><value>Updated information on {0} modules.</value></data>
<data name="UpgradeCannotCombineFlags" xml:space="preserve"><value>The `--dev-build` and `--stable-release` flags cannot be combined!</value></data>
<data name="UpgradeSwitchingToDevBuilds" xml:space="preserve"><value>Switching to dev builds</value></data>
<data name="UpgradeSwitchingToStableReleases" xml:space="preserve"><value>Switching to stable releases</value></data>
Expand Down
2 changes: 1 addition & 1 deletion Core/Relationships/RelationshipResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
/// </summary>
private HashSet<RelationshipDescriptor> suppressedRecommenders = new HashSet<RelationshipDescriptor>();
private readonly HashSet<RelationshipDescriptor> suppressedRecommenders = new HashSet<RelationshipDescriptor>();

private readonly IRegistryQuerier registry;
private readonly GameVersionCriteria versionCrit;
Expand Down
6 changes: 6 additions & 0 deletions Core/Repositories/Repository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

using Newtonsoft.Json;

using CKAN.Games;

namespace CKAN
{
public class Repository : IEquatable<Repository>
Expand Down Expand Up @@ -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);
}
}
3 changes: 1 addition & 2 deletions Core/Repositories/RepositoryDataManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down