/// <summary> /// Tries to parse an identifier in the format Modname=version /// If the module cannot be found in the registry, throws a ModuleNotFoundKraken. /// </summary> public static CkanModule FromIDandVersion(IRegistryQuerier registry, string mod, KspVersionCriteria ksp_version) { CkanModule module; Match match = idAndVersionMatcher.Match(mod); if (match.Success) { string ident = match.Groups["mod"].Value; string version = match.Groups["version"].Value; module = registry.GetModuleByVersion(ident, version); if (module == null || (ksp_version != null && !module.IsCompatibleKSP(ksp_version))) { throw new ModuleNotFoundKraken(ident, version, string.Format("Module {0} version {1} not available", ident, version)); } } else { module = registry.LatestAvailable(mod, ksp_version) ?? registry.InstalledModule(mod)?.Module; if (module == null) { throw new ModuleNotFoundKraken(mod, null, string.Format("Module {0} not installed or available", mod)); } } return(module); }
/// <summary> /// Attempts to convert the identifiers to CkanModules and then calls RelationshipResolver.ctor(IEnumerable{CkanModule}, IEnumerable{CkanModule}, Registry, GameVersion)"/> /// </summary> /// <param name="modulesToInstall">Identifiers of modules to install, will be converted to CkanModules using CkanModule.FromIDandVersion</param> /// <param name="modulesToRemove">Identifiers of modules to remove, will be converted to CkanModules using Registry.InstalledModule</param> /// <param name="options">Options for the RelationshipResolver</param> /// <param name="registry">CKAN registry object for current game instance</param> /// <param name="GameVersion">The current KSP version criteria to consider</param> public RelationshipResolver(IEnumerable <string> modulesToInstall, IEnumerable <string> modulesToRemove, RelationshipResolverOptions options, IRegistryQuerier registry, GameVersionCriteria GameVersion) : this( modulesToInstall?.Select(mod => TranslateModule(mod, options, registry, GameVersion)), modulesToRemove? .Select(mod => { var match = CkanModule.idAndVersionMatcher.Match(mod); return(match.Success ? match.Groups["mod"].Value : mod); }) .Where(identifier => registry.InstalledModule(identifier) != null) .Select(identifier => registry.InstalledModule(identifier).Module), options, registry, GameVersion) { // Does nothing, just calls the other overloaded constructor }
/// <summary> /// Return a mod's current status /// This can't be static because the user's installation plans are part of the object. /// This function is extremely performance-sensitive because it's the default sort for /// the main mod list, so anything in here should be O(1) and fast. /// </summary> /// <param name="manager">Game instance manager containing the instances</param> /// <param name="registry">Registry of instance being displayed</param> /// <param name="identifier">The mod</param> /// <returns> /// Status of mod /// </returns> public InstallStatus GetModStatus(GameInstanceManager manager, IRegistryQuerier registry, string identifier) { if (registry.IsInstalled(identifier, false)) { if (Remove.Contains(identifier)) { return(InstallStatus.Removing); } else if (registry.HasUpdate(identifier, manager.CurrentInstance.VersionCriteria())) { if (Upgrade.Contains(identifier)) { return(InstallStatus.Upgrading); } else { return(InstallStatus.Upgradeable); } } else if (registry.IsAutodetected(identifier)) { return(InstallStatus.AutoDetected); } else if (Replace.Contains(identifier)) { return(InstallStatus.Replacing); } else if (registry.GetReplacement(identifier, manager.CurrentInstance.VersionCriteria()) != null) { return(InstallStatus.Replaceable); } else if (!IsAnyAvailable(registry, identifier)) { return(InstallStatus.Unavailable); } else if (registry.InstalledModule(identifier)?.AutoInstalled ?? false) { return(InstallStatus.AutoInstalled); } else { return(InstallStatus.Installed); } } else { foreach (CkanModule m in Install) { if (m.identifier == identifier) { return(InstallStatus.Installing); } } return(InstallStatus.NotInstalled); } }
/// <summary> /// Initiate the GUI installer flow for one specific module /// </summary> /// <param name="registry">Reference to the registry</param> /// <param name="module">Module to install</param> public async void InstallModuleDriver(IRegistryQuerier registry, CkanModule module) { RelationshipResolverOptions install_ops = RelationshipResolver.DefaultOpts(); install_ops.with_recommends = false; try { var initialChangeSet = new HashSet <ModChange>(); // Install the selected mod initialChangeSet.Add(new ModChange( new GUIMod(module, registry, CurrentInstance.VersionCriteria()), GUIModChangeType.Install, null )); InstalledModule installed = registry.InstalledModule(module.identifier); if (installed != null) { // Already installed, remove it first initialChangeSet.Add(new ModChange( new GUIMod(installed.Module, registry, CurrentInstance.VersionCriteria()), GUIModChangeType.Remove, null )); } List <ModChange> fullChangeSet = new List <ModChange>( await mainModList.ComputeChangeSetFromModList( registry, initialChangeSet, ModuleInstaller.GetInstance(CurrentInstance, Manager.Cache, GUI.user), CurrentInstance.VersionCriteria() ) ); if (fullChangeSet != null && fullChangeSet.Count > 0) { // Resolve the provides relationships in the dependencies installWorker.RunWorkerAsync( new KeyValuePair <List <ModChange>, RelationshipResolverOptions>( fullChangeSet, install_ops ) ); } } catch { // If we failed, do the clean-up normally done by PostInstallMods. HideWaitDialog(false); menuStrip1.Enabled = true; } finally { changeSet = null; } }
/// <summary> /// Initiate the GUI installer flow for one specific module /// </summary> /// <param name="registry">Reference to the registry</param> /// <param name="module">Module to install</param> public async void InstallModuleDriver(IRegistryQuerier registry, CkanModule module) { try { var userChangeSet = new List <ModChange>(); InstalledModule installed = registry.InstalledModule(module.identifier); if (installed != null) { // Already installed, remove it first userChangeSet.Add(new ModChange( new GUIMod(installed.Module, registry, CurrentInstance.VersionCriteria()), GUIModChangeType.Remove, null )); } // Install the selected mod userChangeSet.Add(new ModChange( new GUIMod(module, registry, CurrentInstance.VersionCriteria()), GUIModChangeType.Install, null )); if (userChangeSet.Count > 0) { // Resolve the provides relationships in the dependencies installWorker.RunWorkerAsync( new KeyValuePair <List <ModChange>, RelationshipResolverOptions>( userChangeSet, RelationshipResolver.DependsOnlyOpts() ) ); } } catch { // If we failed, do the clean-up normally done by PostInstallMods. HideWaitDialog(false); menuStrip1.Enabled = true; } finally { changeSet = null; } }
/// <summary> /// Is the mod installed and does it have a newer version compatible with version /// We can't update AD mods /// </summary> public static bool HasUpdate(this IRegistryQuerier querier, string identifier, GameVersionCriteria version) { CkanModule newest_version; try { newest_version = querier.LatestAvailable(identifier, version); } catch (Exception) { return(false); } if (newest_version == null || !querier.IsInstalled(identifier, false) || !newest_version.version.IsGreaterThan(querier.InstalledVersion(identifier))) { return(false); } // All quick checks pass. Now check the relationships. try { var instMod = querier.InstalledModule(identifier); RelationshipResolver resolver = new RelationshipResolver( new CkanModule[] { newest_version }, // Remove the old module when installing the new one instMod == null ? null : new CkanModule[] { instMod.Module }, new RelationshipResolverOptions() { with_recommends = false, without_toomanyprovides_kraken = true, }, querier, version ); } catch (Exception) { return(false); } return(true); }
/// <summary> /// This function returns a changeset based on the selections of the user. /// Currently returns null if a conflict is detected. /// </summary> /// <param name="registry"></param> /// <param name="changeSet"></param> /// <param name="installer">A module installer for the current game instance</param> /// <param name="version">The version of the current game instance</param> public IEnumerable <ModChange> ComputeChangeSetFromModList( IRegistryQuerier registry, HashSet <ModChange> changeSet, ModuleInstaller installer, GameVersionCriteria version) { var modules_to_install = new HashSet <CkanModule>(); var modules_to_remove = new HashSet <CkanModule>(); foreach (var change in changeSet) { switch (change.ChangeType) { case GUIModChangeType.None: break; case GUIModChangeType.Update: case GUIModChangeType.Install: modules_to_install.Add(change.Mod); break; case GUIModChangeType.Remove: modules_to_remove.Add(change.Mod); break; case GUIModChangeType.Replace: ModuleReplacement repl = registry.GetReplacement(change.Mod, version); if (repl != null) { modules_to_remove.Add(repl.ToReplace); modules_to_install.Add(repl.ReplaceWith); } break; default: throw new ArgumentOutOfRangeException(); } } var installed_modules = registry.InstalledModules.Select(imod => imod.Module).ToDictionary(mod => mod.identifier, mod => mod); foreach (var dependent in registry.FindReverseDependencies( modules_to_remove .Select(mod => mod.identifier) .Except(modules_to_install.Select(m => m.identifier)), modules_to_install )) { //TODO This would be a good place to have an event that alters the row's graphics to show it will be removed CkanModule depMod; if (installed_modules.TryGetValue(dependent, out depMod)) { CkanModule module_by_version = registry.GetModuleByVersion(depMod.identifier, depMod.version) ?? registry.InstalledModule(dependent).Module; changeSet.Add(new ModChange(module_by_version, GUIModChangeType.Remove, null)); modules_to_remove.Add(module_by_version); } } foreach (var im in registry.FindRemovableAutoInstalled( registry.InstalledModules.Where(im => !modules_to_remove.Any(m => m.identifier == im.identifier) || modules_to_install.Any(m => m.identifier == im.identifier)) )) { changeSet.Add(new ModChange(im.Module, GUIModChangeType.Remove, new SelectionReason.NoLongerUsed())); modules_to_remove.Add(im.Module); } // Get as many dependencies as we can, but leave decisions and prompts for installation time RelationshipResolverOptions opts = RelationshipResolver.DependsOnlyOpts(); opts.without_toomanyprovides_kraken = true; opts.without_enforce_consistency = true; var resolver = new RelationshipResolver( modules_to_install, modules_to_remove, opts, registry, version); changeSet.UnionWith( resolver.ModList() .Select(m => new ModChange(m, GUIModChangeType.Install, resolver.ReasonFor(m)))); return(changeSet); }
/// <summary> /// This function returns a changeset based on the selections of the user. /// Currently returns null if a conflict is detected. /// </summary> /// <param name="registry"></param> /// <param name="changeSet"></param> /// <param name="installer">A module installer for the current KSP install</param> /// <param name="version">The version of the current KSP install</param> public async Task <IEnumerable <ModChange> > ComputeChangeSetFromModList( IRegistryQuerier registry, HashSet <ModChange> changeSet, ModuleInstaller installer, FactorioVersion version) { var modules_to_install = new HashSet <CfanModule>(); var modules_to_remove = new HashSet <CfanModule>(); var options = new RelationshipResolverOptions { without_toomanyprovides_kraken = false, with_recommends = false }; foreach (var change in changeSet) { switch (change.ChangeType) { case GUIModChangeType.None: break; case GUIModChangeType.Update: case GUIModChangeType.Install: //TODO: Fix //This will give us a mod with a wrong version! modules_to_install.Add(change.Mod.ToCkanModule()); break; case GUIModChangeType.Remove: modules_to_remove.Add(change.Mod); break; default: throw new ArgumentOutOfRangeException(); } } var installed_modules = registry.InstalledModules.Select(imod => imod.Module).ToDictionary(mod => mod.identifier, mod => mod); 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; 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); } else { //TODO Is could be a new type of Kraken. throw kraken; } } foreach (var dependency in registry.FindReverseDependencies(modules_to_remove.Select(mod => mod.identifier))) { //TODO This would be a good place to have a event that alters the row's graphics to show it will be removed CfanModule module_by_version = registry.GetModuleByVersion(installed_modules[dependency].identifier, installed_modules[dependency].modVersion) ?? registry.InstalledModule(dependency).Module; changeSet.Add(new ModChange(new GUIMod(module_by_version, registry, version), GUIModChangeType.Remove, null)); } //May throw InconsistentKraken var resolver = new RelationshipResolver(options, registry, version); resolver.RemoveModsFromInstalledList( changeSet.Where(change => change.ChangeType.Equals(GUIModChangeType.Remove)).Select(m => m.Mod.ToModule())); resolver.AddModulesToInstall(modules_to_install.ToList()); changeSet.UnionWith( resolver.ModList() .Select(m => new ModChange(new GUIMod(m, registry, version), GUIModChangeType.Install, resolver.ReasonFor(m)))); return(changeSet); }
/// <summary> /// This function returns a changeset based on the selections of the user. /// Currently returns null if a conflict is detected. /// </summary> /// <param name="registry"></param> /// <param name="changeSet"></param> /// <param name="installer">A module installer for the current KSP install</param> /// <param name="version">The version of the current KSP install</param> public async Task <IEnumerable <ModChange> > ComputeChangeSetFromModList( IRegistryQuerier registry, HashSet <ModChange> changeSet, ModuleInstaller installer, KspVersionCriteria version) { var modules_to_install = new HashSet <CkanModule>(); var modules_to_remove = new HashSet <CkanModule>(); foreach (var change in changeSet) { switch (change.ChangeType) { case GUIModChangeType.None: break; case GUIModChangeType.Update: case GUIModChangeType.Install: //TODO: Fix //This will give us a mod with a wrong version! modules_to_install.Add(change.Mod.ToCkanModule()); break; case GUIModChangeType.Remove: modules_to_remove.Add(change.Mod); break; case GUIModChangeType.Replace: ModuleReplacement repl = registry.GetReplacement(change.Mod.ToModule(), version); if (repl != null) { modules_to_remove.Add(repl.ToReplace); modules_to_install.Add(repl.ReplaceWith); } break; default: throw new ArgumentOutOfRangeException(); } } var installed_modules = registry.InstalledModules.Select(imod => imod.Module).ToDictionary(mod => mod.identifier, mod => mod); foreach (var dependency in registry.FindReverseDependencies( modules_to_remove .Select(mod => mod.identifier) .Except(modules_to_install.Select(m => m.identifier)) )) { //TODO This would be a good place to have a event that alters the row's graphics to show it will be removed CkanModule module_by_version = registry.GetModuleByVersion(installed_modules[dependency].identifier, installed_modules[dependency].version) ?? registry.InstalledModule(dependency).Module; changeSet.Add(new ModChange(new GUIMod(module_by_version, registry, version), GUIModChangeType.Remove, null)); modules_to_remove.Add(module_by_version); } foreach (var im in registry.FindRemovableAutoInstalled( registry.InstalledModules.Where(im => !modules_to_remove.Any(m => m.identifier == im.identifier)) )) { changeSet.Add(new ModChange(new GUIMod(im.Module, registry, version), GUIModChangeType.Remove, new SelectionReason.NoLongerUsed())); modules_to_remove.Add(im.Module); } bool handled_all_too_many_provides = false; while (!handled_all_too_many_provides) { //Can't await in catch clause - doesn't seem to work in mono. Hence this flag TooManyModsProvideKraken kraken; try { new RelationshipResolver( modules_to_install, modules_to_remove, RelationshipResolver.DependsOnlyOpts(), registry, version); handled_all_too_many_provides = true; continue; } catch (TooManyModsProvideKraken k) { kraken = k; } //Shouldn't get here unless there is a kraken. var mod = await too_many_provides(kraken); if (mod != null) { modules_to_install.Add(mod); } else { //TODO Is could be a new type of Kraken. throw kraken; } } var resolver = new RelationshipResolver( modules_to_install, modules_to_remove, RelationshipResolver.DependsOnlyOpts(), registry, version); changeSet.UnionWith( resolver.ModList() .Select(m => new ModChange(new GUIMod(m, registry, version), GUIModChangeType.Install, resolver.ReasonFor(m)))); return(changeSet); }
/// <summary> /// 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. /// /// See RelationshipResolverOptions for further adjustments that can be made. /// /// </summary> private void ResolveStanza(IEnumerable <RelationshipDescriptor> stanza, SelectionReason reason, RelationshipResolverOptions options, bool soft_resolve = false, IEnumerable <RelationshipDescriptor> old_stanza = null) { if (stanza == null) { return; } foreach (var descriptor in stanza) { string dep_name = descriptor.name; log.DebugFormat("Considering {0}", dep_name); // If we already have this dependency covered, skip. // If it's already installed, skip. if (modlist.ContainsKey(dep_name)) { var module = modlist[dep_name]; if (descriptor.version_within_bounds(module.version)) { continue; } //TODO Ideally we could check here if it can be replaced by the version we want. if (options.procede_with_inconsistencies) { conflicts.Add(new KeyValuePair <CkanModule, CkanModule>(module, reason.Parent)); conflicts.Add(new KeyValuePair <CkanModule, CkanModule>(reason.Parent, module)); continue; } throw new InconsistentKraken( string.Format( "{0} requires a version {1}. However a incompatible version, {2}, is in the resolver", dep_name, descriptor.RequiredVersion, module.version)); } if (registry.IsInstalled(dep_name)) { if (descriptor.version_within_bounds(registry.InstalledVersion(dep_name))) { continue; } var module = registry.InstalledModule(dep_name).Module; //TODO Ideally we could check here if it can be replaced by the version we want. if (options.procede_with_inconsistencies) { conflicts.Add(new KeyValuePair <CkanModule, CkanModule>(module, reason.Parent)); conflicts.Add(new KeyValuePair <CkanModule, CkanModule>(reason.Parent, module)); continue; } throw new InconsistentKraken( string.Format( "{0} requires a version {1}. However a incompatible version, {2}, is already installed", dep_name, descriptor.RequiredVersion, registry.InstalledVersion(dep_name))); } var descriptor1 = descriptor; List <CkanModule> candidates = registry.LatestAvailableWithProvides(dep_name, kspversion, descriptor) .Where(mod => descriptor1.version_within_bounds(mod.version) && MightBeInstallable(mod)).ToList(); if (candidates.Count == 0) { if (!soft_resolve) { log.ErrorFormat("Dependency on {0} found, but nothing provides it.", dep_name); throw new ModuleNotFoundKraken(dep_name); } log.InfoFormat("{0} is recommended/suggested, but nothing provides it.", dep_name); continue; } if (candidates.Count > 1) { // Oh no, too many to pick from! // TODO: It would be great if instead we picked the one with the // most recommendations. if (options.without_toomanyprovides_kraken) { continue; } // If we've got a parent stanza that has a relationship on a mod that provides what // we need, then select that. if (old_stanza != null) { List <CkanModule> provide = candidates.Where(can => old_stanza.Where(relation => can.identifier == relation.name).Any()).ToList(); if (!provide.Any() || provide.Count() > 1) { //We still have either nothing, or too my to pick from //Just throw the TMP now throw new TooManyModsProvideKraken(dep_name, candidates); } candidates[0] = provide.First(); } else { throw new TooManyModsProvideKraken(dep_name, candidates); } } CkanModule candidate = candidates[0]; // Finally, check our candidate against everything which might object // to it being installed; that's all the mods which are fixed in our // list thus far, as well as everything on the system. var fixed_mods = new HashSet <CkanModule>(modlist.Values); fixed_mods.UnionWith(installed_modules); var conflicting_mod = fixed_mods.FirstOrDefault(mod => mod.ConflictsWith(candidate)); if (conflicting_mod == null) { // Okay, looks like we want this one. Adding. Add(candidate, reason); Resolve(candidate, options, stanza); } else if (soft_resolve) { log.InfoFormat("{0} would cause conflicts, excluding it from consideration", candidate); } else { if (options.procede_with_inconsistencies) { Add(candidate, reason); conflicts.Add(new KeyValuePair <CkanModule, CkanModule>(conflicting_mod, candidate)); conflicts.Add(new KeyValuePair <CkanModule, CkanModule>(candidate, conflicting_mod)); } else { throw new InconsistentKraken(string.Format("{0} conflicts with {1}, can't install both.", conflicting_mod, candidate)); } } } }
public int RunCommand(CKAN.GameInstance ksp, object raw_options) { ListOptions options = (ListOptions)raw_options; IRegistryQuerier registry = RegistryManager.Instance(ksp).registry; ExportFileType?exportFileType = null; if (!string.IsNullOrWhiteSpace(options.export)) { exportFileType = GetExportFileType(options.export); if (exportFileType == null) { user.RaiseError("Unknown export format: {0}", options.export); } } if (!(options.porcelain) && exportFileType == null) { user.RaiseMessage("\r\nKSP found at {0}\r\n", ksp.GameDir()); user.RaiseMessage("KSP Version: {0}\r\n", ksp.Version()); user.RaiseMessage("Installed Modules:\r\n"); } if (exportFileType == null) { var installed = new SortedDictionary <string, ModuleVersion>(registry.Installed()); foreach (KeyValuePair <string, ModuleVersion> mod in installed) { ModuleVersion current_version = mod.Value; string modInfo = string.Format("{0} {1}", mod.Key, mod.Value); string bullet = "*"; if (current_version is ProvidesModuleVersion) { // Skip virtuals for now. continue; } else if (current_version is UnmanagedModuleVersion) { // Autodetected dll bullet = "A"; } else { try { // Check if upgrades are available, and show appropriately. log.DebugFormat("Check if upgrades are available for {0}", mod.Key); CkanModule latest = registry.LatestAvailable(mod.Key, ksp.VersionCriteria()); CkanModule current = registry.GetInstalledVersion(mod.Key); InstalledModule inst = registry.InstalledModule(mod.Key); if (latest == null) { // Not compatible! log.InfoFormat("Latest {0} is not compatible", mod.Key); bullet = "X"; if (current == null) { log.DebugFormat(" {0} installed version not found in registry", mod.Key); } // Check if mod is replaceable if (current.replaced_by != null) { ModuleReplacement replacement = registry.GetReplacement(mod.Key, ksp.VersionCriteria()); if (replacement != null) { // Replaceable! bullet = ">"; modInfo = string.Format("{0} > {1} {2}", modInfo, replacement.ReplaceWith.name, replacement.ReplaceWith.version); } } } else if (latest.version.IsEqualTo(current_version)) { // Up to date log.InfoFormat("Latest {0} is {1}", mod.Key, latest.version); bullet = (inst?.AutoInstalled ?? false) ? "+" : "-"; // Check if mod is replaceable if (current.replaced_by != null) { ModuleReplacement replacement = registry.GetReplacement(latest.identifier, ksp.VersionCriteria()); if (replacement != null) { // Replaceable! bullet = ">"; modInfo = string.Format("{0} > {1} {2}", modInfo, replacement.ReplaceWith.name, replacement.ReplaceWith.version); } } } else if (latest.version.IsGreaterThan(mod.Value)) { // Upgradable bullet = "^"; } } catch (ModuleNotFoundKraken) { log.InfoFormat("{0} is installed, but no longer in the registry", mod.Key); bullet = "?"; } } user.RaiseMessage("{0} {1}", bullet, modInfo); } } else { var stream = Console.OpenStandardOutput(); new Exporter(exportFileType.Value).Export(registry, stream); stream.Flush(); } if (!(options.porcelain) && exportFileType == null) { user.RaiseMessage("\r\nLegend: -: Up to date. +:Auto-installed. X: Incompatible. ^: Upgradable. >: Replaceable\r\n A: Autodetected. ?: Unknown. *: Broken. "); // Broken mods are in a state that CKAN doesn't understand, and therefore can't handle automatically } return(Exit.OK); }
/// <summary> /// This function returns a changeset based on the selections of the user. /// Currently returns null if a conflict is detected. /// </summary> /// <param name="registry"></param> /// <param name="changeSet"></param> /// <param name="installer">A module installer for the current KSP install</param> /// <param name="version">The version of the current KSP install</param> public async Task<IEnumerable<ModChange>> ComputeChangeSetFromModList( IRegistryQuerier registry, HashSet<ModChange> changeSet, ModuleInstaller installer, KSPVersion version) { var modules_to_install = new HashSet<CkanModule>(); var modules_to_remove = new HashSet<Module>(); var options = new RelationshipResolverOptions { without_toomanyprovides_kraken = false, with_recommends = false }; foreach (var change in changeSet) { switch (change.ChangeType) { case GUIModChangeType.None: break; case GUIModChangeType.Install: //TODO: Fix //This will give us a mod with a wrong version! modules_to_install.Add(change.Mod.ToCkanModule()); break; case GUIModChangeType.Remove: modules_to_remove.Add(change.Mod); break; case GUIModChangeType.Update: break; default: throw new ArgumentOutOfRangeException(); } } var installed_modules = registry.InstalledModules.Select(imod => imod.Module).ToDictionary(mod => mod.identifier, mod => mod); 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; 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); } else { //TODO Is could be a new type of Kraken. throw kraken; } } foreach (var dependency in registry.FindReverseDependencies(modules_to_remove.Select(mod=>mod.identifier))) { //TODO This would be a good place to have a event that alters the row's graphics to show it will be removed Module module_by_version = registry.GetModuleByVersion(installed_modules[dependency].identifier, installed_modules[dependency].version) ?? registry.InstalledModule(dependency).Module; changeSet.Add(new ModChange(new GUIMod(module_by_version, registry, version), GUIModChangeType.Remove, null)); } //May throw InconsistentKraken var resolver = new RelationshipResolver(options, registry, version); resolver.RemoveModsFromInstalledList( changeSet.Where(change => change.ChangeType.Equals(GUIModChangeType.Remove)).Select(m => m.Mod.ToModule())); resolver.AddModulesToInstall(modules_to_install.ToList()); changeSet.UnionWith( resolver.ModList() .Select(m => new ModChange(new GUIMod(m, registry, version), GUIModChangeType.Install, resolver.ReasonFor(m)))); return changeSet; }