diff --git a/Cmdline/CKAN-cmdline.csproj b/Cmdline/CKAN-cmdline.csproj index ce1a82730b..3cc270858b 100644 --- a/Cmdline/CKAN-cmdline.csproj +++ b/Cmdline/CKAN-cmdline.csproj @@ -9,12 +9,15 @@ CmdLine CKAN.CmdLine.MainClass ..\GUI\assets\ckan.ico + v4.5 + false bin\Debug 4 true + false @@ -63,6 +66,7 @@ + - + \ No newline at end of file diff --git a/Cmdline/Main.cs b/Cmdline/Main.cs index 4c727b15ab..331afe002a 100644 --- a/Cmdline/Main.cs +++ b/Cmdline/Main.cs @@ -20,12 +20,12 @@ internal class MainClass private static readonly ILog log = LogManager.GetLogger(typeof (MainClass)); /* - * When the STAThread is applied, it changes the apartment state of the current thread to be single threaded. + * When the STAThread is applied, it changes the apartment state of the current thread to be single threaded. * Without getting into a huge discussion about COM and threading, * this attribute ensures the communication mechanism between the current thread an * other threads that may want to talk to it via COM. When you're using Windows Forms, * depending on the feature you're using, it may be using COM interop in order to communicate with - * operating system components. Good examples of this are the Clipboard and the File Dialogs. + * operating system components. Good examples of this are the Clipboard and the File Dialogs. */ [STAThread] public static int Main(string[] args) @@ -66,7 +66,7 @@ public static int Main(string[] args) { if (!options.AsRoot) { - user.RaiseError(@"You are trying to run CKAN as root. + user.RaiseError(@"You are trying to run CKAN as root. This is a bad idea and there is absolutely no good reason to do it. Please run CKAN from a user account (or use --asroot if you are feeling brave)."); return Exit.ERROR; } @@ -154,7 +154,7 @@ public static int Main(string[] args) default: break; - } + } #endregion @@ -228,16 +228,16 @@ private static void CheckMonoVersion(IUser user, int rec_major, int rec_minor, i MethodInfo display_name = type.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static); if (display_name != null) - { + { var version_string = (string) display_name.Invoke(null, null); var match = Regex.Match(version_string, @"^\D*(?[\d]+)\.(?\d+)\.(?\d+).*$"); - + if (match.Success) - { + { int major = Int32.Parse(match.Groups["major"].Value); int minor = Int32.Parse(match.Groups["minor"].Value); int patch = Int32.Parse(match.Groups["revision"].Value); - + if (major < rec_major || (major == rec_major && minor < rec_minor)) { user.RaiseMessage( diff --git a/Cmdline/app.config b/Cmdline/app.config new file mode 100644 index 0000000000..51278a4563 --- /dev/null +++ b/Cmdline/app.config @@ -0,0 +1,3 @@ + + + diff --git a/Core/Relationships/RelationshipResolver.cs b/Core/Relationships/RelationshipResolver.cs index 331ee597f0..2274cd5d60 100644 --- a/Core/Relationships/RelationshipResolver.cs +++ b/Core/Relationships/RelationshipResolver.cs @@ -63,8 +63,8 @@ public object Clone() // If we resolved in things breadth-first order, we're less likely to encounter surprises // where a nth-deep recommend blocks a top-level recommend. - // TODO: Add mechanism so that clients can add mods with relationshup other than UserAdded. - // Currently only made to support the with_{} options. + // TODO: Add mechanism so that clients can add mods with relationshup other than UserAdded. + // Currently only made to support the with_{} options. public class RelationshipResolver { // A list of all the mods we're going to install. @@ -111,7 +111,7 @@ public RelationshipResolver(ICollection modules, RelationshipResolve { log.DebugFormat("Preparing to resolve relationships for {0} {1}", module.identifier, module.version); - var module1 = module; //Silence a warning re. closures over foreach var. + var module1 = module; //Silence a warning re. closures over foreach var. foreach (CkanModule listed_mod in modlist.Values.Where(listed_mod => listed_mod.ConflictsWith(module1))) { if (options.procede_with_inconsistencies) @@ -155,7 +155,7 @@ public RelationshipResolver(ICollection modules, RelationshipResolve /// /// Returns the default options for relationship resolution. /// - + // TODO: This should just be able to return a new RelationshipResolverOptions // and the defaults in the class definition should do the right thing. public static RelationshipResolverOptions DefaultOpts() @@ -202,10 +202,10 @@ private void Resolve(CkanModule module, RelationshipResolverOptions options) /// Resolve a relationship stanza (a list of relationships). /// This will add modules to be installed, if required. /// May recurse back to Resolve for those new modules. - /// + /// /// If `soft_resolve` is true, we warn rather than throw exceptions on mods we cannot find. /// If `soft_resolve` is false (default), we throw a ModuleNotFoundKraken if we can't find a dependency. - /// + /// /// Throws a TooManyModsProvideKraken if we have too many choices and /// options.without_toomanyprovides_kraken is not set. /// @@ -232,7 +232,8 @@ private void ResolveStanza(IEnumerable stanza, Relations continue; } - List candidates = registry.LatestAvailableWithProvides(dep_name, kspversion); + List candidates = registry.LatestAvailableWithProvides(dep_name, kspversion) + .Where(mod=>MightBeInstallable(mod)).ToList(); if (candidates.Count == 0) { @@ -334,7 +335,37 @@ private void Add(CkanModule module, Relationship reason) } /// - /// Returns a list of all modules to install to satisify the changes required. + /// Tests that a module might be able to be installed via checking if dependencies + /// exist for current version. + /// + /// The module to consider + /// For internal use + /// If it has dependencies compatible for the current version + private bool MightBeInstallable(CkanModule module, List compatible = null) + { + if (module.depends == null) return true; + if (compatible == null) + { + compatible = new List(); + } + else if (compatible.Contains(module.identifier)) + { + return true; + } + //When checking the dependencies we assume that this module is installable + // in case a dependent depends on it + compatible.Add(module.identifier); + + var needed = module.depends.Select(depend => registry.LatestAvailableWithProvides(depend.name, kspversion)); + //We need every dependency to have at least one possible module + var installable = needed.All(need => need.Any(mod => MightBeInstallable(mod, compatible))); + compatible.Remove(module.identifier); + return installable; + } + + + /// + /// Returns a list of all modules to install to satisfy the changes required. /// public List ModList() { @@ -344,7 +375,7 @@ public List ModList() /// /// Returns a IList consisting of keyValuePairs containing conflicting mods. - /// Note: (a,b) in the list should imply that (b,a) is in the list. + /// Note: (a,b) in the list should imply that (b,a) is in the list. /// public Dictionary ConflictList { @@ -398,14 +429,14 @@ public string ReasonStringFor(Module mod) } /// - /// Used to keep track of the relationships between modules in the resolver. - /// Intended to be used for displaying messages to the user. + /// Used to keep track of the relationships between modules in the resolver. + /// Intended to be used for displaying messages to the user. /// internal abstract class Relationship { //Currently assumed to exist for any relationship other than useradded public virtual CkanModule Parent { get; protected set; } - //Should contain a newline at the end of the string. + //Should contain a newline at the end of the string. public abstract String Reason { get; } diff --git a/GUI/App.config b/GUI/App.config index 009f0e29b6..dcc4cda715 100644 --- a/GUI/App.config +++ b/GUI/App.config @@ -3,9 +3,9 @@ - + - + - \ No newline at end of file + diff --git a/GUI/CKAN-GUI.csproj b/GUI/CKAN-GUI.csproj index 9dba90277a..c5f6454641 100644 --- a/GUI/CKAN-GUI.csproj +++ b/GUI/CKAN-GUI.csproj @@ -27,6 +27,7 @@ false false true + v4.5 false diff --git a/GUI/ErrorDialog.Designer.cs b/GUI/ErrorDialog.Designer.cs index 440b0165a4..b14e10f6ab 100644 --- a/GUI/ErrorDialog.Designer.cs +++ b/GUI/ErrorDialog.Designer.cs @@ -29,7 +29,7 @@ protected override void Dispose(bool disposing) private void InitializeComponent() { this.panel1 = new System.Windows.Forms.Panel(); - this.ErrorMessage = new System.Windows.Forms.Label(); + this.ErrorMessage = new System.Windows.Forms.RichTextBox(); this.DismissButton = new System.Windows.Forms.Button(); this.panel1.SuspendLayout(); this.SuspendLayout(); @@ -50,7 +50,7 @@ private void InitializeComponent() this.ErrorMessage.Size = new System.Drawing.Size(267, 117); this.ErrorMessage.TabIndex = 0; this.ErrorMessage.Text = "Error!"; - this.ErrorMessage.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + this.ErrorMessage.ReadOnly = true; // // DismissButton // @@ -82,7 +82,7 @@ private void InitializeComponent() #endregion private System.Windows.Forms.Panel panel1; - private System.Windows.Forms.Label ErrorMessage; + private System.Windows.Forms.RichTextBox ErrorMessage; private System.Windows.Forms.Button DismissButton; } } \ No newline at end of file diff --git a/GUI/Main.Designer.cs b/GUI/Main.Designer.cs index 71f8b35eef..d714f99465 100644 --- a/GUI/Main.Designer.cs +++ b/GUI/Main.Designer.cs @@ -1162,7 +1162,9 @@ private void InitializeComponent() this.ChooseProvidedModsListView.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { this.columnHeader6, this.columnHeader8}); + this.ChooseProvidedModsListView.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable; this.ChooseProvidedModsListView.Location = new System.Drawing.Point(6, 28); + this.ChooseProvidedModsListView.MultiSelect = false; this.ChooseProvidedModsListView.Name = "ChooseProvidedModsListView"; this.ChooseProvidedModsListView.Size = new System.Drawing.Size(1007, 582); this.ChooseProvidedModsListView.TabIndex = 8; diff --git a/GUI/Main.cs b/GUI/Main.cs index 6ca7dbcced..17e14acc73 100644 --- a/GUI/Main.cs +++ b/GUI/Main.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Threading; +using System.Threading.Tasks; using System.Windows.Forms; using CKAN.Properties; using log4net; @@ -78,8 +79,9 @@ private IEnumerable> ChangeSet get { return change_set; } set { + var orig = change_set; change_set = value; - ChangeSetUpdated(); + if(!ReferenceEquals(orig, value)) ChangeSetUpdated(); } } @@ -88,8 +90,9 @@ private Dictionary Conflicts get { return conflicts; } set { + var orig = conflicts; conflicts = value; - ConflictsUpdated(); + if(orig != value) ConflictsUpdated(); } } @@ -155,7 +158,7 @@ public Main(string[] cmdlineArgs, GUIUser User, bool showConsole) controlFactory = new ControlFactory(); Instance = this; - mainModList = new MainModList(source => UpdateFilters(this)); + mainModList = new MainModList(source => UpdateFilters(this), TooManyModsProvide, User); InitializeComponent(); // We need to initialize error dialog first to display errors @@ -405,6 +408,7 @@ private void MarkAllUpdatesToolButton_Click(object sender, EventArgs e) var mod = ((GUIMod) row.Tag); if (mod.HasUpdate && row.Cells[1] is DataGridViewCheckBoxCell) { + MarkModForUpdate(mod.Identifier); mod.SetUpgradeChecked(row, true); ApplyToolButton.Enabled = true; } @@ -543,10 +547,10 @@ private void ModList_KeyDown(object sender, KeyEventArgs e) /// /// Called on key press when the mod is focused. Scrolls to the first mod - /// with name begining with the key pressed. If more than one unique keys are pressed + /// with name beginning with the key pressed. If more than one unique keys are pressed /// in under a second, it searches for the combination of the keys pressed. /// If the same key is being pressed repeatedly, it cycles through mods names - /// beginnng with that key. If space is pressed, the checkbox at the current row is toggled. + /// beginning with that key. If space is pressed, the checkbox at the current row is toggled. /// private void ModList_KeyPress(object sender, KeyPressEventArgs e) { @@ -557,13 +561,10 @@ private void ModList_KeyPress(object sender, KeyPressEventArgs e) { if (current_row != null && current_row.Selected) { - // Get the checkbox. - var selected_row_check_box = current_row.Cells["Installed"] as DataGridViewCheckBoxCell; - // Invert the value. - if (selected_row_check_box != null) + var gui_mod = ((GUIMod)current_row.Tag); + if (gui_mod.IsInstallable()) { - bool selected_value = (bool)selected_row_check_box.Value; - selected_row_check_box.Value = !selected_value; + MarkModForInstall(gui_mod.Identifier,uninstall:gui_mod.IsInstallChecked); } } e.Handled = true; @@ -644,7 +645,7 @@ private void ModList_CellContentClick(object sender, DataGridViewCellEventArgs e ModList.CommitEdit(DataGridViewDataErrorContexts.Commit); } - private void ModList_CellValueChanged(object sender, DataGridViewCellEventArgs e) + private async void ModList_CellValueChanged(object sender, DataGridViewCellEventArgs e) { if (mainModList.ModFilter == GUIModFilter.Incompatible) { @@ -671,11 +672,13 @@ private void ModList_CellValueChanged(object sender, DataGridViewCellEventArgs e } else if (column_index < 2) { - var gui_mod = ((GUIMod) row.Tag); + var gui_mod = ((GUIMod)row.Tag); switch (column_index) { case 0: gui_mod.SetInstallChecked(row); + if(gui_mod.IsInstallChecked) + last_mod_to_have_install_toggled.Push(gui_mod); break; case 1: gui_mod.SetUpgradeChecked(row); @@ -683,29 +686,44 @@ private void ModList_CellValueChanged(object sender, DataGridViewCellEventArgs e } var registry = registry_manager.registry; - UpdateChangeSetAndConflicts(registry); + await UpdateChangeSetAndConflicts(registry); } } - private void UpdateChangeSetAndConflicts(Registry registry) + private async Task UpdateChangeSetAndConflicts(Registry registry) { - IEnumerable> full_change_set; - Dictionary conflicts; + IEnumerable> full_change_set = null; + Dictionary conflicts = null; + bool too_many_provides_thrown = false; var user_change_set = mainModList.ComputeUserChangeSet(); try { var module_installer = ModuleInstaller.GetInstance(CurrentInstance, GUI.user); - full_change_set = MainModList.ComputeChangeSetFromModList(registry, user_change_set, module_installer, - CurrentInstance.Version()); - conflicts = null; + full_change_set = + await mainModList.ComputeChangeSetFromModList(registry, user_change_set, module_installer, + CurrentInstance.Version()); } catch (InconsistentKraken) { + //Need to be recomputed due to ComputeChangeSetFromModList possibly changing it with too many provides handling. + user_change_set = mainModList.ComputeUserChangeSet(); conflicts = MainModList.ComputeConflictsFromModList(registry, user_change_set, CurrentInstance.Version()); full_change_set = null; } - + catch (TooManyModsProvideKraken) + { + //Can be thrown by ComputeChangeSetFromModList if the user cancels out of it. + //We can just rerun it as the ModInfoTabControl has been removed. + too_many_provides_thrown = true; + } + if (too_many_provides_thrown) + { + await UpdateChangeSetAndConflicts(registry); + conflicts = Conflicts; + full_change_set = ChangeSet; + } + last_mod_to_have_install_toggled.Clear(); Conflicts = conflicts; ChangeSet = full_change_set; } diff --git a/GUI/MainDialogs.cs b/GUI/MainDialogs.cs index f943da362e..ed181b556e 100644 --- a/GUI/MainDialogs.cs +++ b/GUI/MainDialogs.cs @@ -1,11 +1,13 @@ using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading.Tasks; using System.Windows.Forms; namespace CKAN { public partial class Main { - private ErrorDialog m_ErrorDialog; private SettingsDialog m_SettingsDialog; private PluginsDialog m_PluginsDialog; @@ -33,5 +35,44 @@ public bool YesNoDialog(string text) { return m_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. + private readonly ConcurrentStack last_mod_to_have_install_toggled = new ConcurrentStack(); + public async Task 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; + + TaskCompletionSource task = new TaskCompletionSource(); + Util.Invoke(this, () => + { + UpdateProvidedModsDialog(kraken, task); + m_TabController.ShowTab("ChooseProvidedModsTabPage", 3); + m_TabController.SetTabLock(true); + }); + var module = await task.Task; + + if (module == null) + { + last_mod_to_have_install_toggled.TryPeek(out mod); + MarkModForInstall(mod.Identifier,uninstall:true); + } + Util.Invoke(this, () => + { + m_TabController.SetTabLock(false); + + m_TabController.HideTab("ChooseProvidedModsTabPage"); + + m_TabController.ShowTab("ManageModsTabPage"); + }); + + if(module!=null) + MarkModForInstall(module.identifier); + + last_mod_to_have_install_toggled.TryPop(out mod); + return module; + } } } \ No newline at end of file diff --git a/GUI/MainInstall.cs b/GUI/MainInstall.cs index 5bb4976896..d1adab205b 100644 --- a/GUI/MainInstall.cs +++ b/GUI/MainInstall.cs @@ -3,6 +3,7 @@ using System.ComponentModel; using System.Linq; using System.Threading; +using System.Threading.Tasks; using System.Windows.Forms; namespace CKAN @@ -16,7 +17,7 @@ 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 + // this will be the final list of mods we want to install private HashSet toInstall = new HashSet(); private void InstallMods(object sender, DoWorkEventArgs e) // this probably needs to be refactored @@ -29,7 +30,7 @@ private void InstallMods(object sender, DoWorkEventArgs e) // this probably need (KeyValuePair>, RelationshipResolverOptions>) e.Argument; ModuleInstaller installer = ModuleInstaller.GetInstance(CurrentInstance, GUI.user); - // setup progress callback + // setup progress callback toInstall = new HashSet(); var toUninstall = new HashSet(); @@ -103,7 +104,8 @@ private void InstallMods(object sender, DoWorkEventArgs e) // this probably need try { if ( - RegistryManager.Instance(manager.CurrentInstance).registry.LatestAvailable(mod.name, manager.CurrentInstance.Version()) != null && + RegistryManager.Instance(manager.CurrentInstance) + .registry.LatestAvailable(mod.name, manager.CurrentInstance.Version()) != null && !RegistryManager.Instance(manager.CurrentInstance).registry.IsInstalled(mod.name) && !toInstall.Contains(mod.name)) { @@ -192,7 +194,7 @@ private void InstallMods(object sender, DoWorkEventArgs e) // this probably need m_TabController.ShowTab("WaitTabPage"); m_TabController.SetTabLock(true); - + var downloader = new NetAsyncDownloader(GUI.user); cancelCallback = () => { @@ -223,45 +225,15 @@ private void InstallMods(object sender, DoWorkEventArgs e) // this probably need opts.Key); return; } - try - { - var ret = InstallList(toInstall, opts.Value, downloader); - if (!ret) - { - // install failed for some reason, error message is already displayed to the user - e.Result = new KeyValuePair>>(false, - opts.Key); - return; - } - resolvedAllProvidedMods = true; - } - catch (TooManyModsProvideKraken tooManyProvides) + var ret = InstallList(toInstall, opts.Value, downloader); + if (!ret) { - Util.Invoke(this, () => UpdateProvidedModsDialog(tooManyProvides)); - - m_TabController.ShowTab("ChooseProvidedModsTabPage", 3); - m_TabController.SetTabLock(true); - - lock (this) - { - Monitor.Wait(this); - } - - m_TabController.SetTabLock(false); - - m_TabController.HideTab("ChooseProvidedModsTabPage"); - - if (installCanceled) - { - m_TabController.HideTab("WaitTabPage"); - m_TabController.ShowTab("ManageModsTabPage"); - e.Result = new KeyValuePair>>(false, - opts.Key); - return; - } - - m_TabController.ShowTab("WaitTabPage"); + // install failed for some reason, error message is already displayed to the user + e.Result = new KeyValuePair>>(false, + opts.Key); + return; } + resolvedAllProvidedMods = true; } e.Result = new KeyValuePair>>(true, opts.Key); @@ -282,7 +254,9 @@ private bool InstallList(HashSet toInstall, RelationshipResolverOptions } catch (ModuleNotFoundKraken ex) { - GUI.user.RaiseMessage("Module {0} required, but not listed in index, or not available for your version of KSP", ex.module); + GUI.user.RaiseMessage( + "Module {0} required, but not listed in index, or not available for your version of KSP", + ex.module); return false; } catch (BadMetadataKraken ex) @@ -418,8 +392,10 @@ private void PostInstallMods(object sender, RunWorkerCompletedEventArgs e) Util.Invoke(menuStrip1, () => menuStrip1.Enabled = true); } - private void UpdateProvidedModsDialog(TooManyModsProvideKraken tooManyProvides) + private TaskCompletionSource toomany_source; + private void UpdateProvidedModsDialog(TooManyModsProvideKraken tooManyProvides, TaskCompletionSource task) { + toomany_source = task; ChooseProvidedModsLabel.Text = String.Format( "Module {0} is provided by more than one available module, please choose one of the following mods:", @@ -431,41 +407,40 @@ private void UpdateProvidedModsDialog(TooManyModsProvideKraken tooManyProvides) foreach (CkanModule module in tooManyProvides.modules) { - ListViewItem item = new ListViewItem {Tag = module, Checked = true, Text = module.name}; + ListViewItem item = new ListViewItem {Tag = module, Checked = false, Text = module.name}; - ListViewItem.ListViewSubItem description = + ListViewItem.ListViewSubItem description = new ListViewItem.ListViewSubItem {Text = module.@abstract}; item.SubItems.Add(description); ChooseProvidedModsListView.Items.Add(item); } + ChooseProvidedModsListView.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent); + ChooseProvidedModsContinueButton.Enabled = false; } + private void ChooseProvidedModsListView_ItemChecked(object sender, ItemCheckedEventArgs e) { + var any_item_selected = ChooseProvidedModsListView.Items.Cast().Any(item => item.Checked); + ChooseProvidedModsContinueButton.Enabled = any_item_selected; if (!e.Item.Checked) { return; } - foreach (ListViewItem item in ChooseProvidedModsListView.Items) + foreach (ListViewItem item in ChooseProvidedModsListView.Items.Cast() + .Where(item => item != e.Item && item.Checked)) { - if (item != e.Item && item.Checked) - { - item.Checked = false; - } + item.Checked = false; } + } private void ChooseProvidedModsCancelButton_Click(object sender, EventArgs e) { - installCanceled = true; - - lock (this) - { - Monitor.Pulse(this); - } + toomany_source.SetResult(null); } private void ChooseProvidedModsContinueButton_Click(object sender, EventArgs e) @@ -474,16 +449,9 @@ private void ChooseProvidedModsContinueButton_Click(object sender, EventArgs e) { if (item.Checked) { - var identifier = ((CkanModule) item.Tag).identifier; - toInstall.Add(identifier); - break; + toomany_source.SetResult((CkanModule)item.Tag); } } - - lock (this) - { - Monitor.Pulse(this); - } } private void UpdateRecommendedDialog(Dictionary> mods, bool suggested = false) @@ -518,7 +486,7 @@ private void UpdateRecommendedDialog(Dictionary> mods, bool without_enforce_consistency = false, without_toomanyprovides_kraken = true }; - + var resolver = new RelationshipResolver(new List() {pair.Key}, opts, RegistryManager.Instance(manager.CurrentInstance).registry, CurrentInstance.Version()); if (!resolver.ModList().Any()) @@ -526,7 +494,8 @@ private void UpdateRecommendedDialog(Dictionary> mods, bool continue; } - module = RegistryManager.Instance(manager.CurrentInstance).registry.LatestAvailable(pair.Key, CurrentInstance.Version()); + module = RegistryManager.Instance(manager.CurrentInstance) + .registry.LatestAvailable(pair.Key, CurrentInstance.Version()); } catch { diff --git a/GUI/MainModList.cs b/GUI/MainModList.cs index 10ebd26430..180290afbd 100644 --- a/GUI/MainModList.cs +++ b/GUI/MainModList.cs @@ -3,6 +3,7 @@ using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; +using System.Threading.Tasks; using System.Windows.Forms; namespace CKAN @@ -52,15 +53,17 @@ private void _UpdateFilters() // Each time a row in DataGridViewRow is changed, DataGridViewRow updates the view. Which is slow. // To make the filtering process faster, Copy the list of rows. Filter out the hidden and replace t // rows in DataGridView. - var rows = new DataGridViewRow[mainModList.FullListOfModRows.Count]; - mainModList.FullListOfModRows.CopyTo(rows, 0); + + var rows = new DataGridViewRow[mainModList.full_list_of_mod_rows.Count]; + mainModList.full_list_of_mod_rows.CopyTo(rows, 0); // Try to remember the current scroll position and selected mod var scroll_col = Math.Max(0, ModList.FirstDisplayedScrollingColumnIndex); CkanModule selected_mod = null; if (ModList.CurrentRow != null) { - selected_mod = ((GUIMod)ModList.CurrentRow.Tag).ToCkanModule(); + selected_mod = ((GUIMod) ModList.CurrentRow.Tag).ToCkanModule(); } + ModList.Rows.Clear(); foreach (var row in rows) { @@ -141,19 +144,20 @@ private void _UpdateModsList(bool repo_updated) public void MarkModForInstall(string identifier, bool uninstall = false) { - Util.Invoke(this, () => _MarkModForInstall(identifier)); + Util.Invoke(this, () => _MarkModForInstall(identifier, uninstall)); } - private void _MarkModForInstall(string identifier, bool uninstall = false) + private void _MarkModForInstall(string identifier, bool uninstall) { - foreach (DataGridViewRow row in ModList.Rows) + foreach (DataGridViewRow row in mainModList.full_list_of_mod_rows) { var mod = (GUIMod) row.Tag; if (mod.Identifier == identifier) { - mod.IsInstallChecked = true; + mod.IsInstallChecked = !uninstall; //TODO Fix up MarkMod stuff when I commit the GUIConflict (row.Cells[0] as DataGridViewCheckBoxCell).Value = !uninstall; + if (!uninstall) last_mod_to_have_install_toggled.Push(mod); break; } } @@ -180,16 +184,21 @@ public void _MarkModForUpdate(string identifier) public class MainModList { - internal List FullListOfModRows; + internal List full_list_of_mod_rows; - public MainModList(ModFiltersUpdatedEvent onModFiltersUpdated) + public MainModList(ModFiltersUpdatedEvent onModFiltersUpdated, HandleTooManyProvides too_many_provides, + IUser user = null) { + this.too_many_provides = too_many_provides; + this.user = user ?? new NullUser(); Modules = new ReadOnlyCollection(new List()); - ModFiltersUpdated += onModFiltersUpdated != null ? onModFiltersUpdated : (source) => { }; + ModFiltersUpdated += onModFiltersUpdated ?? (source => { }); ModFiltersUpdated(this); } public delegate void ModFiltersUpdatedEvent(MainModList source); + //TODO Move to relationship resolver and have it use this. + public delegate Task HandleTooManyProvides(TooManyModsProvideKraken kraken); public event ModFiltersUpdatedEvent ModFiltersUpdated; public ReadOnlyCollection Modules { get; set; } @@ -230,6 +239,9 @@ public string ModAuthorFilter private GUIModFilter _modFilter = GUIModFilter.Compatible; private string _modNameFilter = String.Empty; private string _modAuthorFilter = String.Empty; + private IUser user; + + private readonly HandleTooManyProvides too_many_provides; /// /// This function returns a changeset based on the selections of the user. @@ -237,15 +249,16 @@ public string ModAuthorFilter /// /// /// - public static IEnumerable> ComputeChangeSetFromModList( + + public async Task>> ComputeChangeSetFromModList( Registry registry, HashSet> changeSet, ModuleInstaller installer, KSPVersion version) { var modules_to_install = new HashSet(); var modules_to_remove = new HashSet(); - var options = new RelationshipResolverOptions() + var options = new RelationshipResolverOptions { - without_toomanyprovides_kraken = true, + without_toomanyprovides_kraken = false, with_recommends = false }; @@ -268,7 +281,42 @@ public static IEnumerable> ComputeCha } } - //May throw InconsistentKraken + + bool handled_all_to_many_provides = false; + while (!handled_all_to_many_provides) + { + //Can't await in catch clause - doesn't seem to work in mono. Hence this flag + TooManyModsProvideKraken kraken = null; + try + { + new RelationshipResolver(modules_to_install.ToList(), options, registry, version); + handled_all_to_many_provides = true; + continue; + } + catch (TooManyModsProvideKraken k) + { + kraken = k; + } + catch (ModuleNotFoundKraken k) + { + //We shouldn't need this. However the relationship provider will throw TMPs with incompatible mods. + user.RaiseError("Module {0} has not been found. This may be because it is not compatible " + + "with the currently installed version of KSP", k.module); + return null; + } + //Shouldn't get here unless there is a kraken. + var mod = await too_many_provides(kraken); + if (mod != null) + { + modules_to_install.Add(mod.identifier); + } + else + { + //TODO Is could be a new type of Kraken. + throw kraken; + } + } + var resolver = new RelationshipResolver(modules_to_install.ToList(), options, registry, version); changeSet.UnionWith( resolver.ModList() @@ -320,7 +368,7 @@ public int CountModsByFilter(GUIModFilter filter) public IEnumerable ConstructModList(IEnumerable modules) { - FullListOfModRows = new List(); + full_list_of_mod_rows = new List(); foreach (var mod in modules) { var item = new DataGridViewRow {Tag = mod}; @@ -330,7 +378,7 @@ public IEnumerable ConstructModList(IEnumerable modules : new DataGridViewTextBoxCell(); installed_cell.Value = mod.IsInstallable() - ? (object)mod.IsInstalled + ? (object) mod.IsInstalled : (mod.IsAutodetected ? "AD" : "-"); var update_cell = mod.HasUpdate && !mod.IsAutodetected @@ -356,9 +404,9 @@ public IEnumerable ConstructModList(IEnumerable modules installed_cell.ReadOnly = !mod.IsInstallable(); update_cell.ReadOnly = !mod.IsInstallable() || !mod.HasUpdate; - FullListOfModRows.Add(item); + full_list_of_mod_rows.Add(item); } - return FullListOfModRows; + return full_list_of_mod_rows; } private bool IsNameInNameFilter(GUIMod mod) diff --git a/Tests/Core/Relationships/RelationshipResolver.cs b/Tests/Core/Relationships/RelationshipResolver.cs index b2a84ab4c8..82553ceb7c 100644 --- a/Tests/Core/Relationships/RelationshipResolver.cs +++ b/Tests/Core/Relationships/RelationshipResolver.cs @@ -8,8 +8,7 @@ namespace Tests.Core.Relationships { - [TestFixture] - public class RelationshipResolverTests + [TestFixture] public class RelationshipResolverTests { private CKAN.Registry registry; private RelationshipResolverOptions options; @@ -43,13 +42,13 @@ public void Constructor_WithConflictingModules() var mod_a = generator.GeneratorRandomModule(); var mod_b = generator.GeneratorRandomModule(conflicts: new List { - new RelationshipDescriptor {name=mod_a.identifier} + new RelationshipDescriptor {name = mod_a.identifier} }); list.Add(mod_a.identifier); list.Add(mod_b.identifier); AddToRegistry(mod_a, mod_b); - + Assert.Throws(() => new RelationshipResolver( list, options, @@ -65,16 +64,14 @@ public void Constructor_WithConflictingModules() Assert.That(resolver.ConflictList, Has.Count.EqualTo(2)); } - [Test] - [Category("Version")] - [Explicit("Versions relationships not implemented")] + [Test, Category("Version"), Explicit("Versions relationships not implemented")] public void Constructor_WithConflictingModulesVersion_Throws() { var list = new List(); var mod_a = generator.GeneratorRandomModule(); var mod_b = generator.GeneratorRandomModule(conflicts: new List { - new RelationshipDescriptor {name=mod_a.identifier, version=mod_a.version.ToString()} + new RelationshipDescriptor {name = mod_a.identifier, version = mod_a.version.ToString()} }); list.Add(mod_a.identifier); @@ -88,18 +85,16 @@ public void Constructor_WithConflictingModulesVersion_Throws() null)); } - [Test] - [Category("Version")] - [Explicit("Versions relationships not implemented")] - [TestCase("1.0", "0.5")] - [TestCase("1.0", "1.0")] + [Test, Category("Version"), Explicit("Versions relationships not implemented")] + [TestCase("1.0", "0.5"), + TestCase("1.0", "1.0")] public void Constructor_WithConflictingModulesVersionMin_Throws(string ver, string conf_min) { var list = new List(); var mod_a = generator.GeneratorRandomModule(version: new Version(ver)); var mod_b = generator.GeneratorRandomModule(conflicts: new List { - new RelationshipDescriptor {name=mod_a.identifier, min_version=conf_min} + new RelationshipDescriptor {name = mod_a.identifier, min_version = conf_min} }); list.Add(mod_a.identifier); @@ -113,18 +108,16 @@ public void Constructor_WithConflictingModulesVersionMin_Throws(string ver, stri null)); } - [Test] - [Category("Version")] - [Explicit("Versions relationships not implemented")] - [TestCase("1.0", "2.0")] - [TestCase("1.0", "1.0")] + [Test, Category("Version"), Explicit("Versions relationships not implemented")] + [TestCase("1.0", "2.0"), + TestCase("1.0", "1.0")] public void Constructor_WithConflictingModulesVersionMax_Throws(string ver, string conf_max) { var list = new List(); var mod_a = generator.GeneratorRandomModule(version: new Version(ver)); var mod_b = generator.GeneratorRandomModule(conflicts: new List { - new RelationshipDescriptor {name=mod_a.identifier, max_version=conf_max} + new RelationshipDescriptor {name = mod_a.identifier, max_version = conf_max} }); list.Add(mod_a.identifier); @@ -138,19 +131,17 @@ public void Constructor_WithConflictingModulesVersionMax_Throws(string ver, stri null)); } - [Test] - [Category("Version")] - [Explicit("Versions relationships not implemented")] - [TestCase("1.0", "0.5", "2.0")] - [TestCase("1.0", "1.0", "2.0")] - [TestCase("1.0", "0.5", "1.0")] + [Test, Category("Version"), Explicit("Versions relationships not implemented")] + [TestCase("1.0", "0.5", "2.0"), + TestCase("1.0", "1.0", "2.0"), + TestCase("1.0", "0.5", "1.0")] public void Constructor_WithConflictingModulesVersionMinMax_Throws(string ver, string conf_min, string conf_max) { var list = new List(); var mod_a = generator.GeneratorRandomModule(version: new Version(ver)); var mod_b = generator.GeneratorRandomModule(conflicts: new List { - new RelationshipDescriptor {name=mod_a.identifier, min_version=conf_min, max_version=conf_max} + new RelationshipDescriptor {name = mod_a.identifier, min_version = conf_min, max_version = conf_max} }); list.Add(mod_a.identifier); @@ -164,18 +155,16 @@ public void Constructor_WithConflictingModulesVersionMinMax_Throws(string ver, s null)); } - [Test] - [Category("Version")] - [Explicit("Versions relationships not implemented")] - [TestCase("1.0", "0.5")] - [TestCase("1.0", "2.0")] + [Test, Category("Version"), Explicit("Versions relationships not implemented")] + [TestCase("1.0", "0.5"), + TestCase("1.0", "2.0")] public void Constructor_WithNonConflictingModulesVersion_DoesNotThrows(string ver, string conf) { var list = new List(); var mod_a = generator.GeneratorRandomModule(version: new Version(ver)); var mod_b = generator.GeneratorRandomModule(conflicts: new List { - new RelationshipDescriptor {name=mod_a.identifier, version=conf} + new RelationshipDescriptor {name = mod_a.identifier, version = conf} }); list.Add(mod_a.identifier); @@ -189,9 +178,7 @@ public void Constructor_WithNonConflictingModulesVersion_DoesNotThrows(string ve null)); } - [Test] - [Category("Version")] - [Explicit("Versions relationships not implemented")] + [Test, Category("Version"), Explicit("Versions relationships not implemented")] [TestCase("1.0", "2.0")] public void Constructor_WithConflictingModulesVersionMin_DoesNotThrows(string ver, string conf_min) { @@ -199,7 +186,7 @@ public void Constructor_WithConflictingModulesVersionMin_DoesNotThrows(string ve var mod_a = generator.GeneratorRandomModule(version: new Version(ver)); var mod_b = generator.GeneratorRandomModule(conflicts: new List { - new RelationshipDescriptor {name=mod_a.identifier, min_version="2.0"} + new RelationshipDescriptor {name = mod_a.identifier, min_version = "2.0"} }); list.Add(mod_a.identifier); @@ -213,9 +200,7 @@ public void Constructor_WithConflictingModulesVersionMin_DoesNotThrows(string ve null)); } - [Test] - [Category("Version")] - [Explicit("Versions relationships not implemented")] + [Test, Category("Version"), Explicit("Versions relationships not implemented")] [TestCase("1.0", "2.0")] public void Constructor_WithConflictingModulesVersionMax_DoesNotThrows(string ver, string conf_max) { @@ -223,7 +208,7 @@ public void Constructor_WithConflictingModulesVersionMax_DoesNotThrows(string ve var mod_a = generator.GeneratorRandomModule(version: new Version(ver)); var mod_b = generator.GeneratorRandomModule(conflicts: new List { - new RelationshipDescriptor {name=mod_a.identifier, max_version=conf_max} + new RelationshipDescriptor {name = mod_a.identifier, max_version = conf_max} }); list.Add(mod_a.identifier); @@ -237,18 +222,17 @@ public void Constructor_WithConflictingModulesVersionMax_DoesNotThrows(string ve null)); } - [Test] - [Category("Version")] - [Explicit("Versions relationships not implemented")] - [TestCase("1.0", "2.0", "3.0")] - [TestCase("4.0", "2.0", "3.0")] - public void Constructor_WithConflictingModulesVersionMinMax_DoesNotThrows(string ver, string conf_min, string conf_max) + [Test, Category("Version"), Explicit("Versions relationships not implemented")] + [TestCase("1.0", "2.0", "3.0"), + TestCase("4.0", "2.0", "3.0")] + public void Constructor_WithConflictingModulesVersionMinMax_DoesNotThrows(string ver, string conf_min, + string conf_max) { var list = new List(); var mod_a = generator.GeneratorRandomModule(version: new Version(ver)); var mod_b = generator.GeneratorRandomModule(conflicts: new List { - new RelationshipDescriptor {name=mod_a.identifier, min_version=conf_min, max_version=conf_max} + new RelationshipDescriptor {name = mod_a.identifier, min_version = conf_min, max_version = conf_max} }); list.Add(mod_a.identifier); @@ -279,7 +263,7 @@ public void Constructor_WithMultipleModulesProviding_Throws() }); var mod_d = generator.GeneratorRandomModule(depends: new List { - new RelationshipDescriptor {name=mod_a.identifier} + new RelationshipDescriptor {name = mod_a.identifier} }); list.Add(mod_d.identifier); @@ -289,7 +273,32 @@ public void Constructor_WithMultipleModulesProviding_Throws() options, registry, null)); + } + [Test] + public void WithMultipleModulesProviding_AllButOneHasUnmatchDependancy_DoesNotThrow() + { + options.without_toomanyprovides_kraken = false; + + var list = new List(); + var mod_a = generator.GeneratorRandomModule(); + var mod_b = generator.GeneratorRandomModule(provides: new List + { + mod_a.identifier + }); + var mod_c = generator.GeneratorRandomModule(provides: new List + { + mod_a.identifier + }, depends: new List {new RelationshipDescriptor {name = "invaild"}}); + var mod_d = generator.GeneratorRandomModule(depends: new List + { + new RelationshipDescriptor {name = mod_a.identifier} + }); + + list.Add(mod_d.identifier); + AddToRegistry(mod_b, mod_c, mod_d); + var mod_list = new RelationshipResolver(list,options,registry,null).ModList(); + Assert.That(mod_list,Contains.Item(mod_b)); } [Test] @@ -304,7 +313,6 @@ public void Constructor_WithMissingModules_Throws() options, registry, null)); - } // Right now our RR always returns the modules it was provided. However @@ -312,7 +320,7 @@ public void Constructor_WithMissingModules_Throws() // return a list *without* them. This isn't a hard error at the moment, // since ModuleInstaller.InstallList will ignore already installed mods, but // it would be nice to have. Discussed a little in GH #521. - [Test][Category("TODO")][Explicit] + [Test, Category("TODO"), Explicit] public void ModList_WithInstalledModules_DoesNotContainThem() { var list = new List(); @@ -378,7 +386,7 @@ public void Constructor_WithConflictingModulesInDependancies_ThrowUnderDefaultSe }); var conflicts_with_dependant = generator.GeneratorRandomModule(conflicts: new List { - new RelationshipDescriptor {name=dependant.identifier} + new RelationshipDescriptor {name = dependant.identifier} }); @@ -460,7 +468,6 @@ public void Constructor_ProvidesSatisfyDependencies() mod_b, depender }); - } @@ -481,16 +488,13 @@ public void Constructor_WithMissingDependants_Throws() options, registry, null)); - } - [Test] - [Category("Version")] - [Explicit("Versions relationships not implemented")] - [TestCase("1.0", "2.0")] - [TestCase("1.0", "0.2")] - [TestCase("0", "0.2")] - [TestCase("1.0", "0")] + [Test, Category("Version"), Explicit("Versions relationships not implemented")] + [TestCase("1.0", "2.0"), + TestCase("1.0", "0.2"), + TestCase("0", "0.2"), + TestCase("1.0", "0")] public void Constructor_WithMissingDependantsVersion_Throws(string ver, string dep) { var list = new List(); @@ -508,12 +512,9 @@ public void Constructor_WithMissingDependantsVersion_Throws(string ver, string d options, registry, null)); - } - [Test] - [Category("Version")] - [Explicit("Versions relationships not implemented")] + [Test, Category("Version"), Explicit("Versions relationships not implemented")] [TestCase("1.0", "2.0")] public void Constructor_WithMissingDependantsVersionMin_Throws(string ver, string dep_min) { @@ -532,12 +533,9 @@ public void Constructor_WithMissingDependantsVersionMin_Throws(string ver, strin options, registry, null)); - } - [Test] - [Category("Version")] - [Explicit("Versions relationships not implemented")] + [Test, Category("Version"), Explicit("Versions relationships not implemented")] [TestCase("1.0", "0.5")] public void Constructor_WithMissingDependantsVersionMax_Throws(string ver, string dep_max) { @@ -556,14 +554,11 @@ public void Constructor_WithMissingDependantsVersionMax_Throws(string ver, strin options, registry, null)); - } - [Test] - [Category("Version")] - [Explicit("Versions relationships not implemented")] - [TestCase("1.0", "2.0", "3.0")] - [TestCase("4.0", "2.0", "3.0")] + [Test, Category("Version"), Explicit("Versions relationships not implemented")] + [TestCase("1.0", "2.0", "3.0"), + TestCase("4.0", "2.0", "3.0")] public void Constructor_WithMissingDependantsVersionMinMax_Throws(string ver, string dep_min, string dep_max) { var list = new List(); @@ -581,19 +576,18 @@ public void Constructor_WithMissingDependantsVersionMinMax_Throws(string ver, st options, registry, null)); - } - [Test] - [Category("Version")] - [Explicit("Versions relationships not implemented")] - [TestCase("1.0", "1.0", "2.0")] - [TestCase("1.0", "1.0", "0.5")]//what to do if a mod is present twice with the same version ? + [Test, Category("Version"), Explicit("Versions relationships not implemented")] + [TestCase("1.0", "1.0", "2.0"), + TestCase("1.0", "1.0", "0.5")] + //what to do if a mod is present twice with the same version ? public void Constructor_WithDependantVersion_ChooseCorrectly(string ver, string dep, string other) { var list = new List(); var dependant = generator.GeneratorRandomModule(version: new Version(ver)); - var other_dependant = generator.GeneratorRandomModule(identifier: dependant.identifier, version: new Version(other)); + var other_dependant = generator.GeneratorRandomModule(identifier: dependant.identifier, + version: new Version(other)); var depender = generator.GeneratorRandomModule(depends: new List { @@ -611,20 +605,18 @@ public void Constructor_WithDependantVersion_ChooseCorrectly(string ver, string dependant, depender }); - } - [Test] - [Category("Version")] - [Explicit("Versions relationships not implemented")] - [TestCase("2.0", "1.0", "0.5")] - [TestCase("2.0", "1.0", "1.5")] - [TestCase("2.0", "2.0", "0.5")] + [Test, Category("Version"), Explicit("Versions relationships not implemented")] + [TestCase("2.0", "1.0", "0.5"), + TestCase("2.0", "1.0", "1.5"), + TestCase("2.0", "2.0", "0.5")] public void Constructor_WithDependantVersionMin_ChooseCorrectly(string ver, string dep_min, string other) { var list = new List(); var dependant = generator.GeneratorRandomModule(version: new Version(ver)); - var other_dependant = generator.GeneratorRandomModule(identifier: dependant.identifier, version: new Version(other)); + var other_dependant = generator.GeneratorRandomModule(identifier: dependant.identifier, + version: new Version(other)); var depender = generator.GeneratorRandomModule(depends: new List { @@ -641,20 +633,18 @@ public void Constructor_WithDependantVersionMin_ChooseCorrectly(string ver, stri dependant, depender }); - } - [Test] - [Category("Version")] - [Explicit("Versions relationships not implemented")] - [TestCase("2.0", "2.0", "0.5")] - [TestCase("2.0", "3.0", "0.5")] - [TestCase("2.0", "3.0", "4.0")] + [Test, Category("Version"), Explicit("Versions relationships not implemented")] + [TestCase("2.0", "2.0", "0.5"), + TestCase("2.0", "3.0", "0.5"), + TestCase("2.0", "3.0", "4.0")] public void Constructor_WithDependantVersionMax_ChooseCorrectly(string ver, string dep_max, string other) { var list = new List(); var dependant = generator.GeneratorRandomModule(version: new Version(ver)); - var other_dependant = generator.GeneratorRandomModule(identifier: dependant.identifier, version: new Version(other)); + var other_dependant = generator.GeneratorRandomModule(identifier: dependant.identifier, + version: new Version(other)); var depender = generator.GeneratorRandomModule(depends: new List { @@ -671,20 +661,19 @@ public void Constructor_WithDependantVersionMax_ChooseCorrectly(string ver, stri dependant, depender }); - } - [Test] - [Category("Version")] - [Explicit("Versions relationships not implemented")] - [TestCase("2.0", "1.0", "3.0", "0.5")] - [TestCase("2.0", "1.0", "3.0", "1.5")] - [TestCase("2.0", "1.0", "3.0", "3.5")] - public void Constructor_WithDependantVersionMinMax_ChooseCorrectly(string ver, string dep_min, string dep_max, string other) + [Test, Category("Version"), Explicit("Versions relationships not implemented"), + TestCase("2.0", "1.0", "3.0", "0.5"), + TestCase("2.0", "1.0", "3.0", "1.5"), + TestCase("2.0", "1.0", "3.0", "3.5")] + public void Constructor_WithDependantVersionMinMax_ChooseCorrectly(string ver, string dep_min, string dep_max, + string other) { var list = new List(); var dependant = generator.GeneratorRandomModule(version: new Version(ver)); - var other_dependant = generator.GeneratorRandomModule(identifier: dependant.identifier, version: new Version(other)); + var other_dependant = generator.GeneratorRandomModule(identifier: dependant.identifier, + version: new Version(other)); var depender = generator.GeneratorRandomModule(depends: new List { @@ -701,7 +690,6 @@ public void Constructor_WithDependantVersionMinMax_ChooseCorrectly(string ver, s dependant, depender }); - } [Test] @@ -733,23 +721,22 @@ public void ReasonFor_WithModsNotInList_ThrowsArgumentException() var relationship_resolver = new RelationshipResolver(list, options, registry, null); var mod_not_in_resolver_list = generator.GeneratorRandomModule(); - CollectionAssert.DoesNotContain(relationship_resolver.ModList(),mod_not_in_resolver_list); + CollectionAssert.DoesNotContain(relationship_resolver.ModList(), mod_not_in_resolver_list); Assert.Throws(() => relationship_resolver.ReasonFor(mod_not_in_resolver_list)); - } [Test] public void ReasonFor_WithUserAddedMods_GivesReasonUserAdded() { var list = new List(); - var mod = generator.GeneratorRandomModule(); + var mod = generator.GeneratorRandomModule(); list.Add(mod.identifier); registry.AddAvailable(mod); AddToRegistry(mod); var relationship_resolver = new RelationshipResolver(list, options, registry, null); var reason = relationship_resolver.ReasonFor(mod); - Assert.That(reason,Is.AssignableTo()); + Assert.That(reason, Is.AssignableTo()); } [Test] @@ -757,31 +744,37 @@ public void ReasonFor_WithSugestedMods_GivesCorrectParent() { var list = new List(); var sugested = generator.GeneratorRandomModule(); - var mod = generator.GeneratorRandomModule(sugests: new List {new RelationshipDescriptor { name = sugested.identifier } } ); - list.Add(mod.identifier); - AddToRegistry(mod,sugested); + var mod = + generator.GeneratorRandomModule(sugests: + new List {new RelationshipDescriptor {name = sugested.identifier}}); + list.Add(mod.identifier); + AddToRegistry(mod, sugested); options.with_all_suggests = true; var relationship_resolver = new RelationshipResolver(list, options, registry, null); var reason = relationship_resolver.ReasonFor(sugested); Assert.That(reason, Is.AssignableTo()); - Assert.That(reason.Parent,Is.EqualTo(mod)); + Assert.That(reason.Parent, Is.EqualTo(mod)); } [Test] public void ReasonFor_WithTreeOfMods_GivesCorrectParents() { - var list = new List(); + var list = new List(); var sugested = generator.GeneratorRandomModule(); var recommendedA = generator.GeneratorRandomModule(); var recommendedB = generator.GeneratorRandomModule(); - var mod = generator.GeneratorRandomModule(sugests: new List { new RelationshipDescriptor { name = sugested.identifier}}); + var mod = + generator.GeneratorRandomModule(sugests: + new List {new RelationshipDescriptor {name = sugested.identifier}}); list.Add(mod.identifier); sugested.recommends = new List - { new RelationshipDescriptor {name=recommendedA.identifier}, - new RelationshipDescriptor { name = recommendedB.identifier}}; + { + new RelationshipDescriptor {name = recommendedA.identifier}, + new RelationshipDescriptor {name = recommendedB.identifier} + }; - AddToRegistry(mod, sugested,recommendedA,recommendedB); + AddToRegistry(mod, sugested, recommendedA, recommendedB); options.with_all_suggests = true; @@ -797,11 +790,6 @@ public void ReasonFor_WithTreeOfMods_GivesCorrectParents() } - - - - - private void AddToRegistry(params CkanModule[] modules) { foreach (var module in modules) @@ -810,4 +798,4 @@ private void AddToRegistry(params CkanModule[] modules) } } } -} +} \ No newline at end of file diff --git a/Tests/GUI/MainModList.cs b/Tests/GUI/MainModList.cs index 0521d46b85..3b816e4abb 100644 --- a/Tests/GUI/MainModList.cs +++ b/Tests/GUI/MainModList.cs @@ -1,10 +1,14 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; using CKAN; using NUnit.Framework; using Tests.Core; using Tests.Data; +using ModuleInstaller = CKAN.ModuleInstaller; namespace Tests.GUI { @@ -14,7 +18,7 @@ public class MainModListTests [Test] public void OnCreation_HasDefaultFilters() { - var item = new MainModList(delegate { }); + var item = new MainModList(delegate { }, delegate { return null; }); Assert.AreEqual(GUIModFilter.Compatible, item.ModFilter, "ModFilter"); Assert.AreEqual(String.Empty, item.ModNameFilter, "ModNameFilter"); } @@ -23,7 +27,7 @@ public void OnCreation_HasDefaultFilters() public void OnModTextFilterChanges_CallsEventHandler() { var called_n = 0; - var item = new MainModList(delegate { called_n++; }); + var item = new MainModList(delegate { called_n++; }, delegate { return null; }); Assert.That(called_n == 1); item.ModNameFilter = "randomString"; Assert.That(called_n == 2); @@ -34,7 +38,7 @@ public void OnModTextFilterChanges_CallsEventHandler() public void OnModTypeFilterChanges_CallsEventHandler() { var called_n = 0; - var item = new MainModList(delegate { called_n++; }); + var item = new MainModList(delegate { called_n++; }, delegate { return null; }); Assert.That(called_n == 1); item.ModFilter = GUIModFilter.Installed; Assert.That(called_n == 2); @@ -45,16 +49,17 @@ public void OnModTypeFilterChanges_CallsEventHandler() [Test] public void ComputeChangeSetFromModList_WithEmptyList_HasEmptyChangeSet() { - using (new DisposableKSP()) - { - var item = new MainModList(delegate { }); + using (var tidy = new DisposableKSP()) + { + KSPManager manager = new KSPManager(new NullUser(), new FakeWin32Registry(tidy.KSP)) { CurrentInstance = tidy.KSP }; + var item = new MainModList(delegate { }, delegate { return null; }); Assert.That(item.ComputeUserChangeSet(), Is.Empty); } } [Test] [Category("Display")] - public void ComputeChangeSetFromModList_WithConflictingMods_ThrowsInconsistentKraken() + public async Task ComputeChangeSetFromModList_WithConflictingMods_ThrowsInconsistentKraken() { using (var tidy = new DisposableKSP()) { @@ -62,17 +67,19 @@ public void ComputeChangeSetFromModList_WithConflictingMods_ThrowsInconsistentKr var registry = Registry.Empty(); var module = TestData.FireSpitterModule(); - module.conflicts = new List { new RelationshipDescriptor { name = "kOS" }}; + module.conflicts = new List() { new RelationshipDescriptor { name = "kOS" } }; registry.AddAvailable(TestData.FireSpitterModule()); registry.AddAvailable(TestData.kOS_014_module()); - registry.RegisterModule(module,Enumerable.Empty(), tidy.KSP ); + registry.RegisterModule(module, Enumerable.Empty(), tidy.KSP); - var main_mod_list = new MainModList(null); + var main_mod_list = new MainModList(null, null); var mod = new GUIMod(TestData.FireSpitterModule(), registry, manager.CurrentInstance.Version()); var mod2 = new GUIMod(TestData.kOS_014_module(), registry, manager.CurrentInstance.Version()); mod.IsInstallChecked = true; mod2.IsInstallChecked = true; - Assert.Throws(()=>MainModList.ComputeChangeSetFromModList(registry,main_mod_list.ComputeUserChangeSet(),null, tidy.KSP.Version())); + + var compute_change_set_from_mod_list = main_mod_list.ComputeChangeSetFromModList(registry, main_mod_list.ComputeUserChangeSet(), null, tidy.KSP.Version()); + await UtilStatic.Throws(async ()=> { await compute_change_set_from_mod_list; }); } } @@ -86,7 +93,7 @@ public void IsVisible_WithAllAndNoNameFilter_ReturnsTrueForCompatible() var ckan_mod = TestData.FireSpitterModule(); var registry = Registry.Empty(); registry.AddAvailable(ckan_mod); - var item = new MainModList(delegate { }); + var item = new MainModList(delegate { }, null); Assert.That(item.IsVisible(new GUIMod(ckan_mod, registry, manager.CurrentInstance.Version()))); } } @@ -94,14 +101,14 @@ public void IsVisible_WithAllAndNoNameFilter_ReturnsTrueForCompatible() [Test] public void CountModsByFilter_EmptyModList_ReturnsZeroForAllFilters() { - var item = new MainModList(delegate { }); + var item = new MainModList(delegate { }, null); foreach (GUIModFilter filter in Enum.GetValues(typeof(GUIModFilter))) { Assert.That(item.CountModsByFilter(filter), Is.EqualTo(0)); } } - + [Test] [Category("Display")] public void ConstructModList_NumberOfRows_IsEqualToNumberOfMods() @@ -112,7 +119,7 @@ public void ConstructModList_NumberOfRows_IsEqualToNumberOfMods() var registry = Registry.Empty(); registry.AddAvailable(TestData.FireSpitterModule()); registry.AddAvailable(TestData.kOS_014_module()); - var main_mod_list = new MainModList(null); + var main_mod_list = new MainModList(null, null); var mod_list = main_mod_list.ConstructModList(new List { new GUIMod(TestData.FireSpitterModule(), registry, manager.CurrentInstance.Version()), @@ -120,6 +127,42 @@ public void ConstructModList_NumberOfRows_IsEqualToNumberOfMods() }); Assert.That(mod_list, Has.Count.EqualTo(2)); } - } + } + + [Test] + [Category("Display")] + public async Task TooManyProvidesCallsHandlers() + { + using (var tidy = new DisposableKSP()) + { + var registry = Registry.Empty(); + var generator = new RandomModuleGenerator(new Random(0451)); + var provide_ident = "provide"; + var mod = generator.GeneratorRandomModule(depends: new List() + { + new RelationshipDescriptor() {name = provide_ident} + }); + var moda = generator.GeneratorRandomModule(provides: new List { provide_ident }); + var modb = generator.GeneratorRandomModule(provides: new List { provide_ident }); + var choice_of_provide = modb; + registry.AddAvailable(mod); + registry.AddAvailable(moda); + registry.AddAvailable(modb); + var installer = ModuleInstaller.GetInstance(tidy.KSP, null); + var main_mod_list = new MainModList(null, async kraken => choice_of_provide); + var a = new HashSet>() + { + new KeyValuePair(mod,GUIModChangeType.Install) + }; + + var mod_list = await main_mod_list.ComputeChangeSetFromModList(registry, a, installer, null); + CollectionAssert.AreEquivalent( + new[] { + new KeyValuePair(mod,GUIModChangeType.Install), + new KeyValuePair(modb,GUIModChangeType.Install)}, mod_list); + + } + } + } } diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index 11ad62b3c4..22312b79b1 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -7,8 +7,9 @@ Library false CKAN.Tests - v4.0 + v4.5 512 + true @@ -18,6 +19,7 @@ DEBUG;TRACE prompt 4 + false pdbonly @@ -26,6 +28,7 @@ TRACE prompt 4 + false Tests @@ -134,6 +137,7 @@ + @@ -165,4 +169,4 @@ - + \ No newline at end of file diff --git a/Tests/Util.cs b/Tests/Util.cs new file mode 100644 index 0000000000..8b1870ee0b --- /dev/null +++ b/Tests/Util.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using NUnit.Framework; + +namespace Tests +{ + [TestFixture] public class Util + { + [Test] + public void AssembliesHaveNoAsyncVoids() + { + UtilStatic.AssertNoAsyncVoidMethods(GetType().Assembly); + } + } + + public static class UtilStatic + { + private static bool HasAttribute(this MethodInfo method) where TAttribute : Attribute + { + return method.GetCustomAttributes(typeof (TAttribute), false).Any(); + } + + public static void AssertNoAsyncVoidMethods(Assembly assembly) + { + var messages = assembly + .GetAsyncVoidMethods() + .Select(method => + string.Format("'{0}.{1}' is an async void method.", + method.DeclaringType.Name, + method.Name)) + .ToList(); + Assert.False(messages.Any(), + "Async void methods found!" + Environment.NewLine + String.Join(Environment.NewLine, messages)); + } + + private static IEnumerable GetAsyncVoidMethods(this Assembly assembly) + { + return assembly.GetLoadableTypes() + .SelectMany(type => type.GetMethods( + BindingFlags.NonPublic + | BindingFlags.Public + | BindingFlags.Instance + | BindingFlags.Static + | BindingFlags.DeclaredOnly)) + .Where(method => method.HasAttribute()) + .Where(method => method.ReturnType == typeof (void)); + } + + private static IEnumerable GetLoadableTypes(this Assembly assembly) + { + if (assembly == null) throw new ArgumentNullException("assembly"); + try + { + return assembly.GetTypes(); + } + catch (ReflectionTypeLoadException e) + { + return e.Types.Where(t => t != null); + } + } + + public static async Task Throws(Func async) where T : Exception + { + try + { + await async(); + Assert.Fail("Expected exception of type: {0}", typeof (T)); + } + catch (T) + { + return; + } + } + } +} \ No newline at end of file