Skip to content

Commit

Permalink
Merge #3892 Show recommendations of full changeset with opt-out
Browse files Browse the repository at this point in the history
  • Loading branch information
HebaruSan committed Aug 30, 2023
2 parents bfac8dc + fd496ea commit 029cc52
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 53 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file.
- [Multiple] Support multiple download URLs per module (#3877 by: HebaruSan; reviewed: techman83)
- [Multiple] French translation updates from Crowdin (#3879 by: vinix38; reviewed: HebaruSan)
- [Multiple] Improve handling of unregistered files at uninstall (#3890 by: HebaruSan; reviewed: techman83)
- [Multiple] Show recommendations of full changeset with opt-out (#3892 by: HebaruSan; reviewed: techman83)

### Bugfixes

Expand Down
4 changes: 4 additions & 0 deletions CKAN.schema
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,10 @@
"choice_help_text": {
"description" : "Optional help text shown when user has to choose among multiple modules",
"type" : "string"
},
"suppress_recommendations": {
"description" : "If true, don't check this mod or its dependencies for recommendations or suggestions",
"type" : "boolean"
}
},
"required" : [ "name" ]
Expand Down
60 changes: 35 additions & 25 deletions Core/ModuleInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1308,6 +1308,20 @@ private void DownloadModules(IEnumerable<CkanModule> mods, IDownloader downloade
}
}

private static readonly RelationshipResolverOptions RecommenderOptions = new RelationshipResolverOptions()
{
// Only look at depends
with_recommends = false,

// Don't throw anything
without_toomanyprovides_kraken = true,
without_enforce_consistency = true,
proceed_with_inconsistencies = true,

// Skip relationships with suppress_recommendations==true
get_recommenders = true,
};

/// <summary>
/// Looks for optional related modules that could be installed alongside the given modules
/// </summary>
Expand All @@ -1325,22 +1339,24 @@ public bool FindRecommendations(
Registry registry,
out Dictionary<CkanModule, Tuple<bool, List<string>>> recommendations,
out Dictionary<CkanModule, List<string>> suggestions,
out Dictionary<CkanModule, HashSet<string>> supporters
)
out Dictionary<CkanModule, HashSet<string>> supporters)
{
Dictionary<CkanModule, List<string>> dependersIndex = getDependersIndex(sourceModules, registry, toInstall);
// Get all dependencies except where suppress_recommendations==true
var resolver = new RelationshipResolver(sourceModules, null, RecommenderOptions,
registry, ksp.VersionCriteria());
var recommenders = resolver.ModList().ToList();

var dependersIndex = getDependersIndex(recommenders, registry, toInstall);
var instList = toInstall.ToList();
recommendations = new Dictionary<CkanModule, Tuple<bool, List<string>>>();
suggestions = new Dictionary<CkanModule, List<string>>();
supporters = new Dictionary<CkanModule, HashSet<string>>();
foreach (CkanModule mod in sourceModules.Where(m => m.recommends != null))
foreach (CkanModule mod in recommenders.Where(m => m.recommends != null))
{
foreach (RelationshipDescriptor rel in mod.recommends)
{
List<CkanModule> providers = rel.LatestAvailableWithProvides(
registry,
ksp.VersionCriteria()
);
registry, ksp.VersionCriteria());
int i = 0;
foreach (CkanModule provider in providers)
{
Expand All @@ -1355,21 +1371,18 @@ out Dictionary<CkanModule, HashSet<string>> supporters
provider,
new Tuple<bool, List<string>>(
!provider.IsDLC && (i == 0 || provider.identifier == (rel as ModuleRelationshipDescriptor)?.name),
dependers)
);
dependers));
++i;
}
}
}
}
foreach (CkanModule mod in sourceModules.Where(m => m.suggests != null))
foreach (CkanModule mod in recommenders.Where(m => m.suggests != null))
{
foreach (RelationshipDescriptor rel in mod.suggests)
{
List<CkanModule> providers = rel.LatestAvailableWithProvides(
registry,
ksp.VersionCriteria()
);
registry, ksp.VersionCriteria());
foreach (CkanModule provider in providers)
{
if (!registry.IsInstalled(provider.identifier)
Expand All @@ -1388,7 +1401,7 @@ out Dictionary<CkanModule, HashSet<string>> supporters
// Find installable modules with "supports" relationships
var candidates = registry.CompatibleModules(ksp.VersionCriteria())
.Where(mod => !registry.IsInstalled(mod.identifier)
&& !toInstall.Any(m => m.identifier == mod.identifier))
&& !toInstall.Any(m => m.identifier == mod.identifier))
.Where(m => m?.supports != null)
.Except(recommendations.Keys)
.Except(suggestions.Keys);
Expand All @@ -1397,7 +1410,7 @@ out Dictionary<CkanModule, HashSet<string>> supporters
{
foreach (RelationshipDescriptor rel in mod.supports)
{
if (rel.MatchesAny(sourceModules, null, null))
if (rel.MatchesAny(recommenders, null, null))
{
var name = (rel as ModuleRelationshipDescriptor)?.name;
if (!string.IsNullOrEmpty(name))
Expand All @@ -1414,11 +1427,11 @@ out Dictionary<CkanModule, HashSet<string>> supporters
}
}
}
supporters.RemoveWhere(kvp => !CanInstall(
RelationshipResolver.DependsOnlyOpts(),
instList.Concat(new List<CkanModule>() { kvp.Key }).ToList(),
registry
));
supporters.RemoveWhere(kvp =>
!CanInstall(
RelationshipResolver.DependsOnlyOpts(),
instList.Concat(new List<CkanModule>() { kvp.Key }).ToList(),
registry));

return recommendations.Any() || suggestions.Any() || supporters.Any();
}
Expand All @@ -1427,8 +1440,7 @@ out Dictionary<CkanModule, HashSet<string>> supporters
private Dictionary<CkanModule, List<string>> getDependersIndex(
IEnumerable<CkanModule> sourceModules,
IRegistryQuerier registry,
List<CkanModule> toExclude
)
List<CkanModule> toExclude)
{
Dictionary<CkanModule, List<string>> dependersIndex = new Dictionary<CkanModule, List<string>>();
foreach (CkanModule mod in sourceModules)
Expand All @@ -1440,9 +1452,7 @@ List<CkanModule> toExclude
foreach (RelationshipDescriptor rel in relations)
{
List<CkanModule> providers = rel.LatestAvailableWithProvides(
registry,
ksp.VersionCriteria()
);
registry, ksp.VersionCriteria());
foreach (CkanModule provider in providers)
{
if (!registry.IsInstalled(provider.identifier)
Expand Down
19 changes: 15 additions & 4 deletions Core/Relationships/RelationshipResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,15 @@ public class RelationshipResolverOptions : ICloneable
/// </summary>
public bool allow_incompatible = false;

public object Clone()
{
return MemberwiseClone();
}
/// <summary>
/// If true, get the list of mods that should be checked for
/// recommendations and suggestions.
/// Differs from normal resolution in that it stops when
/// ModuleRelationshipDescriptor.suppress_recommendations==true
/// </summary>
public bool get_recommenders = false;

public object Clone() => MemberwiseClone();
}

// TODO: RR currently conducts a depth-first resolution of requirements. While we do the
Expand Down Expand Up @@ -403,6 +408,12 @@ private void ResolveStanza(IEnumerable<RelationshipDescriptor> stanza, Selection
{
log.DebugFormat("Considering {0}", descriptor.ToString());

if (options.get_recommenders && descriptor.suppress_recommendations)
{
log.DebugFormat("Skipping {0} because get_recommenders option is set");
continue;
}

// If we already have this dependency covered,
// resolve its relationships if we haven't already.
if (descriptor.MatchesAny(modlist.Values, null, null, out CkanModule installingCandidate))
Expand Down
50 changes: 28 additions & 22 deletions Core/Types/RelationshipDescriptor.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.Serialization;

using Newtonsoft.Json;

using CKAN.Extensions;
using CKAN.Versioning;

Expand All @@ -12,18 +16,14 @@ public abstract class RelationshipDescriptor : IEquatable<RelationshipDescriptor
public bool MatchesAny(
IEnumerable<CkanModule> modules,
HashSet<string> dlls,
IDictionary<string, ModuleVersion> dlc
)
{
return MatchesAny(modules, dlls, dlc, out CkanModule _);
}
IDictionary<string, ModuleVersion> dlc)
=> MatchesAny(modules, dlls, dlc, out CkanModule _);

public abstract bool MatchesAny(
IEnumerable<CkanModule> modules,
HashSet<string> dlls,
IDictionary<string, ModuleVersion> dlc,
out CkanModule matched
);
out CkanModule matched);

public abstract bool WithinBounds(CkanModule otherModule);

Expand All @@ -44,25 +44,35 @@ public abstract CkanModule ExactMatch(
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string choice_help_text;

/// <summary>
/// If true, then don't show recommendations and suggestions of this module or its dependencies.
/// Otherwise recommendations and suggestions of everything in changeset will be included.
/// This is meant to allow the KSP-RO team to shorten the prompts that appear during their installation.
/// </summary>
[JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)]
[DefaultValue(false)]
public bool suppress_recommendations;

// virtual ToString() already present in 'object'
}

public class ModuleRelationshipDescriptor : RelationshipDescriptor
{
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public ModuleVersion max_version;

[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public ModuleVersion min_version;
//Why is the identifier called name?
public /* required */ string name;

// The identifier to match
public string name;

[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public ModuleVersion version;

public override bool WithinBounds(CkanModule otherModule)
{
return otherModule.ProvidesList.Contains(name)
&& WithinBounds(otherModule.version);
}
=> otherModule.ProvidesList.Contains(name)
&& WithinBounds(otherModule.version);

/// <summary>
/// Returns if the other version satisfies this RelationshipDescriptor.
Expand Down Expand Up @@ -113,8 +123,7 @@ public override bool MatchesAny(
IEnumerable<CkanModule> modules,
HashSet<string> dlls,
IDictionary<string, ModuleVersion> dlc,
out CkanModule matched
)
out CkanModule matched)
{
modules = modules?.AsCollection();

Expand Down Expand Up @@ -189,7 +198,8 @@ public override bool Equals(RelationshipDescriptor other)
&& max_version == modRel.max_version;
}

public override bool ContainsAny(IEnumerable<string> identifiers) => identifiers.Contains(name);
public override bool ContainsAny(IEnumerable<string> identifiers)
=> identifiers.Contains(name);

public override bool StartsWith(string prefix)
=> name.IndexOf(prefix, StringComparison.CurrentCultureIgnoreCase) == 0;
Expand Down Expand Up @@ -231,17 +241,13 @@ public class AnyOfRelationshipDescriptor : RelationshipDescriptor
};

public override bool WithinBounds(CkanModule otherModule)
{
return any_of?.Any(r => r.WithinBounds(otherModule))
?? false;
}
=> any_of?.Any(r => r.WithinBounds(otherModule)) ?? false;

public override bool MatchesAny(
IEnumerable<CkanModule> modules,
HashSet<string> dlls,
IDictionary<string, ModuleVersion> dlc,
out CkanModule matched
)
out CkanModule matched)
{
if (any_of != null)
{
Expand Down
3 changes: 1 addition & 2 deletions GUI/Main/MainInstall.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,7 @@ private void InstallMods(object sender, DoWorkEventArgs e)
registry,
out Dictionary<CkanModule, Tuple<bool, List<string>>> recommendations,
out Dictionary<CkanModule, List<string>> suggestions,
out Dictionary<CkanModule, HashSet<string>> supporters
))
out Dictionary<CkanModule, HashSet<string>> supporters))
{
tabController.ShowTab("ChooseRecommendedModsTabPage", 3);
ChooseRecommendedMods.LoadRecommendations(
Expand Down
11 changes: 11 additions & 0 deletions Spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,17 @@ depends:
choice_help_text: Pick ModA if you prefer polka dots, ModB otherwise
```
(**v1.34**) Clients implementing version `v1.34` or later of the spec *must* support
the `suppress_recommendations` property in a relationship. If this property is `true`,
then recommendations or suggestions will not be shown for the module satisfying this
relationship or its (sole) dependencies.

```yaml
depends:
- name: OtherMod
suppress_recommendations: true
```

##### depends

A list of mods which are *required* for the current mod to operate.
Expand Down

0 comments on commit 029cc52

Please sign in to comment.