/// <summary> /// The core of the module upgrading logic, with callbacks to /// support different input formats managed by the calling code. /// Handles transactions, creating commonly required objects, /// looping logic, prompting for TooManyModsProvideKraken resolution. /// </summary> /// <param name="manager">Game instance manager to use</param> /// <param name="user">IUser object for output</param> /// <param name="ksp">Game instance to use</param> /// <param name="attemptUpgradeCallback">Function to call to try to perform the actual upgrade, may throw TooManyModsProvideKraken</param> /// <param name="addUserChoiceCallback">Function to call when the user has requested a new module added to the change set in response to TooManyModsProvideKraken</param> private static void UpgradeModules( GameInstanceManager manager, IUser user, CKAN.GameInstance ksp, AttemptUpgradeAction attemptUpgradeCallback, System.Action <CkanModule> addUserChoiceCallback) { using (TransactionScope transact = CkanTransaction.CreateTransactionScope()) { var installer = new ModuleInstaller(ksp, manager.Cache, user); var downloader = new NetAsyncModulesDownloader(user, manager.Cache); var regMgr = RegistryManager.Instance(ksp); HashSet <string> possibleConfigOnlyDirs = null; bool done = false; while (!done) { try { attemptUpgradeCallback?.Invoke(installer, downloader, regMgr, ref possibleConfigOnlyDirs); transact.Complete(); done = true; } catch (TooManyModsProvideKraken k) { int choice = user.RaiseSelectionDialog( k.Message, k.modules.Select(m => $"{m.identifier} ({m.name})").ToArray()); if (choice < 0) { return; } else { addUserChoiceCallback?.Invoke(k.modules[choice]); } } } } }
/// <summary> /// Run the screen /// </summary> /// <param name="process">Framework parameter not used by this object</param> public override void Run(Action process = null) { HashSet <string> rejected = new HashSet <string>(); DrawBackground(); using (TransactionScope trans = CkanTransaction.CreateTransactionScope()) { bool retry = false; do { Draw(); try { // Reset this so we stop unless an exception sets it to true retry = false; // GUI prompts user to choose recs/sugs, // CmdLine assumes recs and ignores sugs if (plan.Install.Count > 0) { // Track previously rejected optional dependencies and don't prompt for them again. DependencyScreen ds = new DependencyScreen(manager, plan, rejected, debug); if (ds.HaveOptions()) { LaunchSubScreen(ds); } } // FUTURE: BackgroundWorker HashSet <string> possibleConfigOnlyDirs = null; RegistryManager regMgr = RegistryManager.Instance(manager.CurrentInstance); ModuleInstaller inst = ModuleInstaller.GetInstance(manager.CurrentInstance, manager.Cache, this); inst.onReportModInstalled = OnModInstalled; if (plan.Remove.Count > 0) { inst.UninstallList(plan.Remove, ref possibleConfigOnlyDirs, regMgr, true, plan.Install); plan.Remove.Clear(); } NetAsyncModulesDownloader dl = new NetAsyncModulesDownloader(this, manager.Cache); if (plan.Upgrade.Count > 0) { inst.Upgrade(plan.Upgrade, dl, ref possibleConfigOnlyDirs, regMgr); plan.Upgrade.Clear(); } if (plan.Install.Count > 0) { List <CkanModule> iList = new List <CkanModule>(plan.Install); inst.InstallList(iList, resolvOpts, regMgr, ref possibleConfigOnlyDirs, dl); plan.Install.Clear(); } if (plan.Replace.Count > 0) { inst.Replace(AllReplacements(plan.Replace), resolvOpts, dl, ref possibleConfigOnlyDirs, regMgr, true); } trans.Complete(); // Don't let the installer re-use old screen references inst.User = null; inst.onReportModInstalled = null; } catch (CancelledActionKraken) { // Don't need to tell the user they just cancelled out. } catch (FileNotFoundKraken ex) { // Possible file corruption RaiseError(ex.Message); } catch (DirectoryNotFoundKraken ex) { RaiseError(ex.Message); } catch (FileExistsKraken ex) { if (ex.owningModule != null) { RaiseMessage($"{ex.installingModule} tried to install {ex.filename}, but {ex.owningModule} has already installed it."); RaiseMessage($"Please report this problem at https://github.com/KSP-CKAN/NetKAN/issues/new/choose"); } else { RaiseMessage($"{ex.installingModule} tried to install {ex.filename}, but it is already installed."); RaiseMessage($"Please manually uninstall the mod that owns this file to install {ex.installingModule}."); } RaiseError("Game files reverted."); } catch (DownloadErrorsKraken ex) { RaiseError(ex.ToString()); } catch (ModuleDownloadErrorsKraken ex) { RaiseError(ex.ToString()); } catch (DownloadThrottledKraken ex) { if (RaiseYesNoDialog($"{ex.ToString()}\n\nEdit authentication tokens now?")) { if (ex.infoUrl != null) { ModInfoScreen.LaunchURL(ex.infoUrl); } LaunchSubScreen(new AuthTokenScreen()); } } catch (MissingCertificateKraken ex) { RaiseError(ex.ToString()); } catch (InconsistentKraken ex) { RaiseError(ex.InconsistenciesPretty); } catch (TooManyModsProvideKraken ex) { ConsoleChoiceDialog <CkanModule> ch = new ConsoleChoiceDialog <CkanModule>( $"Module {ex.requested} is provided by multiple modules. Which would you like to install?", "Name", ex.modules, (CkanModule mod) => mod.ToString() ); CkanModule chosen = ch.Run(); DrawBackground(); if (chosen != null) { // Use chosen to continue installing plan.Install.Add(chosen); retry = true; } } catch (BadMetadataKraken ex) { RaiseError($"Bad metadata detected for {ex.module}: {ex.Message}"); } catch (DependencyNotSatisfiedKraken ex) { RaiseError($"{ex.parent} requires {ex.module}, but it is not listed in the index, or not available for your version of KSP.\r\n{ex.Message}"); } catch (ModuleNotFoundKraken ex) { RaiseError($"Module {ex.module} required but it is not listed in the index, or not available for your version of KSP.\r\n{ex.Message}"); } catch (ModNotInstalledKraken ex) { RaiseError($"{ex.mod} is not installed, can't remove"); } } while (retry); } }