Skip to content

Commit

Permalink
Merge #4085 Conflicting recommendations check for ConsoleUI
Browse files Browse the repository at this point in the history
  • Loading branch information
HebaruSan committed May 9, 2024
2 parents 671613b + 8d50fd7 commit 88d208f
Show file tree
Hide file tree
Showing 14 changed files with 116 additions and 110 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ All notable changes to this project will be documented in this file.
- [ConsoleUI] Play game option for ConsoleUI (#4064 by: HebaruSan)
- [ConsoleUI] ConsoleUI prompt to delete non-empty folders after uninstall (#4066 by: HebaruSan)
- [Multiple] Treat mods with missing files as upgradeable/reinstallable (#4067 by: HebaruSan)
- [ConsoleUI] Conflicting recommendations check for ConsoleUI (#4085 by: HebaruSan)

### Bugfixes

Expand Down
2 changes: 1 addition & 1 deletion ConsoleUI/AuthTokenListScreen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public AuthTokenScreen() : base()
/// </summary>
protected override string LeftHeader()
{
return $"CKAN {Meta.GetVersion()}";
return $"{Meta.GetProductName()} {Meta.GetVersion()}";
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion ConsoleUI/DeleteDirectoriesScreen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ private void PopulateFiles()
/// <summary>
/// Put CKAN 1.25.5 in top left corner
/// </summary>
protected override string LeftHeader() => $"CKAN {Meta.GetVersion()}";
protected override string LeftHeader() => $"{Meta.GetProductName()} {Meta.GetVersion()}";

/// <summary>
/// Show the Delete Directories header
Expand Down
198 changes: 101 additions & 97 deletions ConsoleUI/DependencyScreen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
using System.Linq;
using System.ComponentModel;
using System.Collections.Generic;

using CKAN.Versioning;
#if NETFRAMEWORK
using CKAN.Extensions;
#endif
using CKAN.ConsoleUI.Toolkit;

namespace CKAN.ConsoleUI {
Expand All @@ -16,31 +21,26 @@ public class DependencyScreen : ConsoleScreen {
/// Initialize the screen
/// </summary>
/// <param name="mgr">Game instance manager containing instances</param>
/// <param name="registry">Registry of the current instance for finding mods</param>
/// <param name="reg">Registry of the current instance for finding mods</param>
/// <param name="cp">Plan of mods to add and remove</param>
/// <param name="rej">Mods that the user saw and did not select, in this pass or a previous pass</param>
/// <param name="dbg">True if debug options should be available, false otherwise</param>
public DependencyScreen(GameInstanceManager mgr, Registry registry, ChangePlan cp, HashSet<string> rej, bool dbg) : base()
public DependencyScreen(GameInstanceManager mgr, Registry reg, ChangePlan cp, HashSet<string> rej, bool dbg) : base()
{
debug = dbg;
manager = mgr;
plan = cp;
this.registry = registry;
installer = new ModuleInstaller(manager.CurrentInstance, manager.Cache, this);
rejected = rej;

AddObject(new ConsoleLabel(
1, 2, -1,
() => Properties.Resources.RecommendationsLabel
));

HashSet<CkanModule> sourceModules = new HashSet<CkanModule>();
sourceModules.UnionWith(plan.Install);
sourceModules.UnionWith(new HashSet<CkanModule>(
ReplacementIdentifiers(plan.Replace)
.Select(id => registry.InstalledModule(id).Module)
));
generateList(sourceModules);
debug = dbg;
manager = mgr;
plan = cp;
registry = reg;
rejected = rej;

AddObject(new ConsoleLabel(1, 2, -1,
() => Properties.Resources.RecommendationsLabel));

generateList(new ModuleInstaller(manager.CurrentInstance, manager.Cache, this),
plan.Install
.Concat(ReplacementModules(plan.Replace,
manager.CurrentInstance.VersionCriteria()))
.ToHashSet());

dependencyList = new ConsoleListBox<Dependency>(
1, 4, -1, -2,
Expand All @@ -49,32 +49,37 @@ public DependencyScreen(GameInstanceManager mgr, Registry registry, ChangePlan c
new ConsoleListBoxColumn<Dependency>() {
Header = Properties.Resources.RecommendationsInstallHeader,
Width = 7,
Renderer = (Dependency d) => StatusSymbol(d.module)
Renderer = (Dependency d) => StatusSymbol(d.module),
},
new ConsoleListBoxColumn<Dependency>() {
Header = Properties.Resources.RecommendationsNameHeader,
Width = null,
Renderer = (Dependency d) => d.module.ToString()
Renderer = (Dependency d) => d.module.ToString(),
},
new ConsoleListBoxColumn<Dependency>() {
Header = Properties.Resources.RecommendationsSourcesHeader,
Width = 42,
Renderer = (Dependency d) => string.Join(", ", d.dependents)
Renderer = (Dependency d) => string.Join(", ", d.dependents),
}
},
1, 0, ListSortDirection.Descending
);
dependencyList.AddTip("+", Properties.Resources.Toggle);
dependencyList.AddBinding(Keys.Plus, (object sender, ConsoleTheme theme) => {
ChangePlan.toggleContains(accepted, dependencyList.Selection.module);
var mod = dependencyList.Selection.module;
if (accepted.Contains(mod) || TryWithoutConflicts(accepted.Append(mod))) {
ChangePlan.toggleContains(accepted, mod);
}
return true;
});

dependencyList.AddTip($"{Properties.Resources.Ctrl}+A", Properties.Resources.SelectAll);
dependencyList.AddBinding(Keys.CtrlA, (object sender, ConsoleTheme theme) => {
foreach (var kvp in dependencies) {
if (!accepted.Contains(kvp.Key)) {
ChangePlan.toggleContains(accepted, kvp.Key);
if (TryWithoutConflicts(dependencies.Keys)) {
foreach (var kvp in dependencies) {
if (!accepted.Contains(kvp.Key)) {
ChangePlan.toggleContains(accepted, kvp.Key);
}
}
}
return true;
Expand All @@ -89,10 +94,9 @@ public DependencyScreen(GameInstanceManager mgr, Registry registry, ChangePlan c
dependencyList.AddTip(Properties.Resources.Enter, Properties.Resources.Details);
dependencyList.AddBinding(Keys.Enter, (object sender, ConsoleTheme theme) => {
if (dependencyList.Selection != null) {
LaunchSubScreen(theme, new ModInfoScreen(
manager, registry, plan,
dependencyList.Selection.module,
debug));
LaunchSubScreen(theme, new ModInfoScreen(manager, reg, plan,
dependencyList.Selection.module,
debug));
}
return true;
});
Expand All @@ -102,120 +106,125 @@ public DependencyScreen(GameInstanceManager mgr, Registry registry, ChangePlan c
AddTip(Properties.Resources.Esc, Properties.Resources.Cancel);
AddBinding(Keys.Escape, (object sender, ConsoleTheme theme) => {
// Add everything to rejected
foreach (var kvp in dependencies) {
rejected.Add(kvp.Key.identifier);
}
rejected.UnionWith(dependencies.Keys.Select(m => m.identifier));
return false;
});

AddTip("F9", Properties.Resources.Accept);
AddBinding(Keys.F9, (object sender, ConsoleTheme theme) => {
foreach (CkanModule mod in accepted) {
plan.Install.Add(mod);
}
// Add the rest to rejected
foreach (var kvp in dependencies) {
if (!accepted.Contains(kvp.Key)) {
rejected.Add(kvp.Key.identifier);
}
if (TryWithoutConflicts(accepted)) {
plan.Install.UnionWith(accepted);
// Add the rest to rejected
rejected.UnionWith(dependencies.Keys
.Except(accepted)
.Select(m => m.identifier));
return false;
}
return false;
return true;
});
}

/// <summary>
/// Put CKAN 1.25.5 in top left corner
/// </summary>
protected override string LeftHeader()
{
return $"CKAN {Meta.GetVersion()}";
}
protected override string LeftHeader() => $"{Meta.GetProductName()} {Meta.GetVersion()}";

/// <summary>
/// Put description in top center
/// </summary>
protected override string CenterHeader()
{
return Properties.Resources.RecommendationsTitle;
}
protected override string CenterHeader() => Properties.Resources.RecommendationsTitle;

/// <summary>
/// Return whether there are any options to show.
/// ModListScreen uses this to avoid showing this screen when empty.
/// </summary>
public bool HaveOptions()
{
return dependencies.Count > 0;
}
public bool HaveOptions() => dependencies.Count > 0;

private void generateList(HashSet<CkanModule> inst)
private void generateList(ModuleInstaller installer, HashSet<CkanModule> inst)
{
if (installer.FindRecommendations(
inst, new List<CkanModule>(inst), registry as Registry,
out Dictionary<CkanModule, Tuple<bool, List<string>>> recommendations,
out Dictionary<CkanModule, List<string>> suggestions,
out Dictionary<CkanModule, HashSet<string>> supporters
)) {
foreach (var kvp in recommendations) {
dependencies.Add(kvp.Key, new Dependency() {
module = kvp.Key,
defaultInstall = kvp.Value.Item1,
dependents = kvp.Value.Item2.OrderBy(d => d).ToList()
foreach ((CkanModule mod, Tuple<bool, List<string>> checkedAndDependents) in recommendations) {
dependencies.Add(mod, new Dependency() {
module = mod,
dependents = checkedAndDependents.Item2.OrderBy(d => d).ToList()
});
if (kvp.Value.Item1) {
accepted.Add(kvp.Key);
}
}
foreach (var kvp in suggestions) {
dependencies.Add(kvp.Key, new Dependency() {
module = kvp.Key,
defaultInstall = false,
dependents = kvp.Value.OrderBy(d => d).ToList()
foreach ((CkanModule mod, List<string> dependents) in suggestions) {
dependencies.Add(mod, new Dependency() {
module = mod,
dependents = dependents.OrderBy(d => d).ToList()
});
}
foreach (var kvp in supporters) {
dependencies.Add(kvp.Key, new Dependency() {
module = kvp.Key,
defaultInstall = false,
dependents = kvp.Value.OrderBy(d => d).ToList()
foreach ((CkanModule mod, HashSet<string> dependents) in supporters) {
dependencies.Add(mod, new Dependency() {
module = mod,
dependents = dependents.OrderBy(d => d).ToList()
});
}
// Check the default checkboxes
accepted.UnionWith(recommendations.Where(kvp => kvp.Value.Item1)
.Select(kvp => kvp.Key));
}
}

private IEnumerable<string> ReplacementIdentifiers(IEnumerable<string> replaced_identifiers)
private IEnumerable<CkanModule> ReplacementModules(IEnumerable<string> replaced_identifiers,
GameVersionCriteria crit)
=> replaced_identifiers.Select(replaced => registry.GetReplacement(replaced, crit))
.Where(repl => repl != null)
.Select(repl => repl.ReplaceWith);

private string StatusSymbol(CkanModule mod)
=> accepted.Contains(mod) ? installing
: notinstalled;

private bool TryWithoutConflicts(IEnumerable<CkanModule> toAdd)
{
foreach (string replaced in replaced_identifiers) {
ModuleReplacement repl = registry.GetReplacement(
replaced, manager.CurrentInstance.VersionCriteria()
);
if (repl != null) {
yield return repl.ReplaceWith.identifier;
}
if (HasConflicts(toAdd, out List<string> conflictDescriptions)) {
RaiseError("{0}", string.Join(Environment.NewLine,
conflictDescriptions));
return false;
}
return true;
}

private string StatusSymbol(CkanModule mod)
private bool HasConflicts(IEnumerable<CkanModule> toAdd,
out List<string> descriptions)
{
return accepted.Contains(mod)
? installing
: notinstalled;
try
{
var resolver = new RelationshipResolver(
plan.Install.Concat(toAdd).Distinct(),
plan.Remove.Select(ident => registry.InstalledModule(ident)?.Module),
RelationshipResolverOptions.ConflictsOpts(), registry,
manager.CurrentInstance.VersionCriteria());
descriptions = resolver.ConflictDescriptions.ToList();
return descriptions.Count > 0;
}
catch (DependencyNotSatisfiedKraken k)
{
descriptions = new List<string>() { k.Message };
return true;
}
}

private readonly HashSet<CkanModule> accepted = new HashSet<CkanModule>();
private readonly HashSet<string> rejected;

private readonly IRegistryQuerier registry;
private readonly GameInstanceManager manager;
private readonly ModuleInstaller installer;
private readonly ChangePlan plan;
private readonly bool debug;

private readonly Dictionary<CkanModule, Dependency> dependencies = new Dictionary<CkanModule, Dependency>();
private readonly ConsoleListBox<Dependency> dependencyList;

private static readonly string notinstalled = " ";
private static readonly string installing = "+";
private const string notinstalled = " ";
private const string installing = "+";
}

/// <summary>
Expand All @@ -224,14 +233,9 @@ private string StatusSymbol(CkanModule mod)
public class Dependency {

/// <summary>
/// Identifier of mod
/// </summary>
public CkanModule module;

/// <summary>
/// True if we default to installing, false otherwise
/// The mod
/// </summary>
public bool defaultInstall;
public CkanModule module;

/// <summary>
/// List of mods that recommended or suggested this mod
Expand Down
2 changes: 1 addition & 1 deletion ConsoleUI/ExitScreen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ private void Draw(ConsoleTheme theme)

// Specially formatted snippets
var ckanPiece = new FancyLinePiece(Meta.GetProductName(), theme.ExitInnerBg, theme.ExitHighlightFg);
var ckanVersionPiece = new FancyLinePiece($"CKAN {Meta.GetVersion()}", theme.ExitInnerBg, theme.ExitHighlightFg);
var ckanVersionPiece = new FancyLinePiece($"{Meta.GetProductName()} {Meta.GetVersion()}", theme.ExitInnerBg, theme.ExitHighlightFg);
var releaseLinkPiece = new FancyLinePiece("https://github.com/KSP-CKAN/CKAN/releases/latest", theme.ExitInnerBg, theme.ExitLinkFg);
var issuesLinkPiece = new FancyLinePiece("https://github.com/KSP-CKAN/CKAN/issues", theme.ExitInnerBg, theme.ExitLinkFg);
var authorsLinkPiece = new FancyLinePiece("https://github.com/KSP-CKAN/CKAN/graphs/contributors", theme.ExitInnerBg, theme.ExitLinkFg);
Expand Down
2 changes: 1 addition & 1 deletion ConsoleUI/GameInstanceListScreen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ public GameInstanceListScreen(GameInstanceManager mgr, RepositoryDataManager rep
/// </summary>
protected override string LeftHeader()
{
return $"CKAN {Meta.GetVersion()}";
return $"{Meta.GetProductName()} {Meta.GetVersion()}";
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion ConsoleUI/GameInstanceScreen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ protected GameInstanceScreen(GameInstanceManager mgr, string initName = "", stri
/// </summary>
protected override string LeftHeader()
{
return $"CKAN {Meta.GetVersion()}";
return $"{Meta.GetProductName()} {Meta.GetVersion()}";
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion ConsoleUI/InstallFiltersScreen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public InstallFiltersScreen(IConfiguration globalConfig, GameInstance instance)
/// </summary>
protected override string LeftHeader()
{
return $"CKAN {Meta.GetVersion()}";
return $"{Meta.GetProductName()} {Meta.GetVersion()}";
}

/// <summary>
Expand Down
3 changes: 2 additions & 1 deletion ConsoleUI/InstallScreen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ public override void Run(ConsoleTheme theme, Action<ConsoleTheme> process = null
registry.InstalledModules
.Select(im => im.Module)
.ToArray(),
plan.Install))
plan.Install)
?? m)
.ToArray();
inst.InstallList(iList, resolvOpts, regMgr, ref possibleConfigOnlyDirs, dl);
plan.Install.Clear();
Expand Down
Loading

0 comments on commit 88d208f

Please sign in to comment.