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

Show recommendations of installed modules in GUI #2577

Merged
merged 3 commits into from
Nov 21, 2018
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
2 changes: 2 additions & 0 deletions GUI/CKAN-GUI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@
<Compile Include="MainModList.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="MainRecommendations.cs">
</Compile>
<Compile Include="MainRepo.cs">
<SubType>Form</SubType>
</Compile>
Expand Down
12 changes: 12 additions & 0 deletions GUI/Main.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 0 additions & 17 deletions GUI/Main.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1018,11 +1018,6 @@ private void ChangesListView_SelectedIndexChanged(object sender, EventArgs e)
ShowSelectionModInfo(ChangesListView.SelectedItems);
}

private void RecommendedModsListView_SelectedIndexChanged(object sender, EventArgs e)
{
ShowSelectionModInfo(RecommendedModsListView.SelectedItems);
}

private void ChooseProvidedModsListView_SelectedIndexChanged(object sender, EventArgs e)
{
ShowSelectionModInfo(ChooseProvidedModsListView.SelectedItems);
Expand Down Expand Up @@ -1054,18 +1049,6 @@ private void MainTabControl_OnSelectedIndexChanged(object sender, EventArgs e)
}
}

private void RecommendedModsToggleCheckbox_CheckedChanged(object sender, EventArgs e)
{
var state = ((CheckBox)sender).Checked;
foreach (ListViewItem item in RecommendedModsListView.Items)
{
if (item.Checked != state)
item.Checked = state;
}

RecommendedModsListView.Refresh();
}

private void reportAnIssueToolStripMenuItem_Click(object sender, EventArgs e)
{
Process.Start("https://github.com/KSP-CKAN/NetKAN/issues/new");
Expand Down
22 changes: 14 additions & 8 deletions GUI/MainDialogs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,23 +40,29 @@ public bool YesNoDialog(string text)
return yesNoDialog.ShowYesNoDialog(text) == DialogResult.Yes;
}

//Ugly Hack. Possible fix is to alter the relationship provider so we can use a loop
//over reason for to find a user requested mod. Or, you know, pass in a handler to it.
// Ugly Hack. Possible fix is to alter the relationship provider so we can use a loop
// over reason for to find a user requested mod. Or, you know, pass in a handler to it.
private readonly ConcurrentStack<GUIMod> last_mod_to_have_install_toggled = new ConcurrentStack<GUIMod>();
public async Task<CkanModule> TooManyModsProvide(TooManyModsProvideKraken kraken)
{
//We want LMtHIT to be the last user selection. If we alter this handling a too many provides
// it needs to be reset so a potential second too many provides doesn't use the wrong mod.
GUIMod mod;

private async Task<CkanModule> TooManyModsProvideCore(TooManyModsProvideKraken kraken)
{
TaskCompletionSource<CkanModule> task = new TaskCompletionSource<CkanModule>();
Util.Invoke(this, () =>
{
UpdateProvidedModsDialog(kraken, task);
tabController.ShowTab("ChooseProvidedModsTabPage", 3);
tabController.SetTabLock(true);
});
var module = await task.Task;
return await task.Task;
}

public async Task<CkanModule> TooManyModsProvide(TooManyModsProvideKraken kraken)
{
// We want LMtHIT to be the last user selection. If we alter this handling a too many provides
// it needs to be reset so a potential second too many provides doesn't use the wrong mod.
GUIMod mod;

var module = await TooManyModsProvideCore(kraken);

if (module == null
&& last_mod_to_have_install_toggled.TryPeek(out mod))
Expand Down
225 changes: 30 additions & 195 deletions GUI/MainInstall.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ public partial class Main
// this may happen on the recommended/suggested mods dialogs
private volatile bool installCanceled;

// this will be the final list of mods we want to install
private HashSet<CkanModule> toInstall = new HashSet<CkanModule>();

/// <summary>
/// Initiate the GUI installer flow for one specific module
/// </summary>
Expand Down Expand Up @@ -90,7 +87,8 @@ private void InstallMods(object sender, DoWorkEventArgs e) // this probably need
installer.onReportModInstalled += OnModInstalled;
// setup progress callback

toInstall = new HashSet<CkanModule>();
// this will be the final list of mods we want to install
HashSet<CkanModule> toInstall = new HashSet<CkanModule>();
var toUninstall = new HashSet<string>();
var toUpgrade = new HashSet<string>();

Expand Down Expand Up @@ -120,13 +118,13 @@ private void InstallMods(object sender, DoWorkEventArgs e) // this probably need
{
if (change.ChangeType == GUIModChangeType.Install)
{
AddMod(change.Mod.ToModule().recommends, recommended, change.Mod.Identifier, registry);
AddMod(change.Mod.ToModule().suggests, suggested, change.Mod.Identifier, registry);
AddMod(change.Mod.ToModule().recommends, recommended, change.Mod.Identifier, registry, toInstall);
AddMod(change.Mod.ToModule().suggests, suggested, change.Mod.Identifier, registry, toInstall);
}
}

ShowSelection(recommended);
ShowSelection(suggested, true);
ShowSelection(recommended, toInstall);
ShowSelection(suggested, toInstall, true);

tabController.HideTab("ChooseRecommendedModsTabPage");

Expand Down Expand Up @@ -174,6 +172,30 @@ private void InstallMods(object sender, DoWorkEventArgs e) // this probably need
}
resolvedAllProvidedMods = true;
}
catch (TooManyModsProvideKraken k)
{
// Prompt user to choose which mod to use
CkanModule chosen = TooManyModsProvideCore(k).Result;
// Close the selection prompt
Util.Invoke(this, () =>
{
tabController.ShowTab("WaitTabPage");
tabController.HideTab("ChooseProvidedModsTabPage");
});
if (chosen != null)
{
// User picked a mod, queue it up for installation
toInstall.Add(chosen);
// DON'T return so we can loop around and try the above InstallList call again
}
else
{
// User cancelled, get out
tabController.ShowTab("ManageModsTabPage");
e.Result = new KeyValuePair<bool, ModChanges>(false, opts.Key);
return;
}
}
catch (DependencyNotSatisfiedKraken ex)
{
GUI.user.RaiseMessage(
Expand Down Expand Up @@ -294,80 +316,6 @@ private void InstallMods(object sender, DoWorkEventArgs e) // this probably need
}
}

private void AddMod(
IEnumerable<RelationshipDescriptor> relations,
Dictionary<CkanModule, List<string>> chooseAble,
string identifier,
IRegistryQuerier registry)
{
if (relations == null)
return;
foreach (RelationshipDescriptor rel in relations)
{
List<CkanModule> providers = registry.LatestAvailableWithProvides(
rel.name,
CurrentInstance.VersionCriteria(),
rel
);
foreach (CkanModule provider in providers)
{
if (!registry.IsInstalled(provider.identifier)
&& !toInstall.Any(m => m.identifier == provider.identifier))
{
// We want to show this mod to the user. Add it.
List<string> dependers;
if (chooseAble.TryGetValue(provider, out dependers))
{
// Add the dependent mod to the list of reasons this dependency is shown.
dependers.Add(identifier);
}
else
{
// Add a new entry if this provider isn't listed yet.
chooseAble.Add(provider, new List<string>() { identifier });
}
}
}
}
}

private void ShowSelection(Dictionary<CkanModule, List<string>> selectable, bool suggest = false)
{
if (installCanceled)
return;

// If we're going to install something anyway, then don't list it in the
// recommended list, since they can't de-select it anyway.
// The ToList dance is because we need to modify the dictionary while iterating over it,
// and C# doesn't like that.
foreach (CkanModule module in selectable.Keys.ToList())
{
if (toInstall.Any(m => m.identifier == module.identifier))
{
selectable.Remove(module);
}
}

Dictionary<CkanModule, string> mods = GetShowableMods(selectable);

// If there are any mods that would be recommended, prompt the user to make
// selections.
if (mods.Any())
{
Util.Invoke(this, () => UpdateRecommendedDialog(mods, suggest));

tabController.ShowTab("ChooseRecommendedModsTabPage", 3);
tabController.SetTabLock(true);

lock (this)
{
Monitor.Wait(this);
}

tabController.SetTabLock(false);
}
}

private void OnModInstalled(CkanModule mod)
{
AddStatusMessage("Module \"{0}\" successfully installed", mod.name);
Expand Down Expand Up @@ -485,118 +433,5 @@ private void ChooseProvidedModsContinueButton_Click(object sender, EventArgs e)
}
}

/// <summary>
/// Tries to get every mod in the Dictionary, which can be installed
/// It also transforms the Recommender list to a string
/// </summary>
/// <param name="mods"></param>
/// <returns></returns>
private Dictionary<CkanModule, string> GetShowableMods(Dictionary<CkanModule, List<string>> mods)
{
Dictionary<CkanModule, string> modules = new Dictionary<CkanModule, string>();

var opts = new RelationshipResolverOptions
{
with_all_suggests = false,
with_recommends = false,
with_suggests = false,
without_enforce_consistency = false,
without_toomanyprovides_kraken = true
};

foreach (var pair in mods)
{
try
{
RelationshipResolver resolver = new RelationshipResolver(
new List<CkanModule> { pair.Key },
null,
opts,
RegistryManager.Instance(manager.CurrentInstance).registry,
CurrentInstance.VersionCriteria()
);

if (resolver.ModList().Any())
{
// Resolver was able to find a way to install, so show it to the user
modules.Add(pair.Key, String.Join(",", pair.Value.ToArray()));
}
}
catch { }
}
return modules;
}

private void UpdateRecommendedDialog(Dictionary<CkanModule, string> mods, bool suggested = false)
{
if (!suggested)
{
RecommendedDialogLabel.Text =
"The following modules have been recommended by one or more of the chosen modules:";
RecommendedModsListView.Columns[1].Text = "Recommended by:";
RecommendedModsToggleCheckbox.Text = "(De-)select all recommended mods.";
RecommendedModsToggleCheckbox.Checked=true;
tabController.RenameTab("ChooseRecommendedModsTabPage", "Choose recommended mods");
}
else
{
RecommendedDialogLabel.Text =
"The following modules have been suggested by one or more of the chosen modules:";
RecommendedModsListView.Columns[1].Text = "Suggested by:";
RecommendedModsToggleCheckbox.Text = "(De-)select all suggested mods.";
RecommendedModsToggleCheckbox.Checked=false;
tabController.RenameTab("ChooseRecommendedModsTabPage", "Choose suggested mods");
}

RecommendedModsListView.Items.Clear();
foreach (var pair in mods)
{
CkanModule module = pair.Key;
ListViewItem item = new ListViewItem()
{
Tag = module,
Checked = !suggested,
Text = Manager.Cache.IsMaybeCachedZip(pair.Key)
? $"{pair.Key.name} {pair.Key.version} (cached)"
: $"{pair.Key.name} {pair.Key.version} ({pair.Key.download.Host ?? ""}, {CkanModule.FmtSize(pair.Key.download_size)})"
};

ListViewItem.ListViewSubItem recommendedBy = new ListViewItem.ListViewSubItem() { Text = pair.Value };

item.SubItems.Add(recommendedBy);

ListViewItem.ListViewSubItem description = new ListViewItem.ListViewSubItem {Text = module.@abstract};

item.SubItems.Add(description);

RecommendedModsListView.Items.Add(item);
}
}

private void RecommendedModsContinueButton_Click(object sender, EventArgs e)
{
foreach (ListViewItem item in RecommendedModsListView.Items)
{
if (item.Checked)
{
toInstall.Add((CkanModule) item.Tag);
}
}

lock (this)
{
Monitor.Pulse(this);
}
}

private void RecommendedModsCancelButton_Click(object sender, EventArgs e)
{
installCanceled = true;

lock (this)
{
Monitor.Pulse(this);
}
}
}
}
Loading