private void Download() { ProgressScreen ps = new ProgressScreen($"Downloading {mod.identifier}"); NetAsyncModulesDownloader dl = new NetAsyncModulesDownloader(ps); ModuleInstaller inst = ModuleInstaller.GetInstance(manager.CurrentInstance, manager.Cache, ps); LaunchSubScreen( ps, () => { try { dl.DownloadModules(manager.Cache, new List <CkanModule> { mod }); if (!manager.Cache.IsMaybeCachedZip(mod)) { ps.RaiseError("Download failed, file is corrupted"); } } catch (Exception ex) { ps.RaiseError($"Download failed: {ex}"); } } ); // Don't let the installer re-use old screen references inst.User = null; }
private void CacheMod(object sender, DoWorkEventArgs e) { ResetProgress(); ClearLog(); NetAsyncModulesDownloader dowloader = new NetAsyncModulesDownloader(currentUser); dowloader.DownloadModules(CurrentInstance.Cache, new List<CkanModule> { (CkanModule)e.Argument }); e.Result = e.Argument; }
/// <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); } }
/// <summary> /// Makes sure all the specified mods are downloaded. /// </summary> private void DownloadModules(IEnumerable<CkanModule> mods, NetAsyncModulesDownloader downloader) { List<CkanModule> downloads = mods.Where(module => !ksp.Cache.IsCachedZip(module.download)).ToList(); if (downloads.Count > 0) { downloader.DownloadModules(ksp.Cache, downloads); } }
/// <summary> /// Upgrades or installs the mods listed to the specified versions for the user's KSP. /// Will *re-install* or *downgrade* (with a warning) as well as upgrade. /// Throws ModuleNotFoundKraken if a module is not installed. /// </summary> public void Upgrade(IEnumerable<CkanModule> modules, NetAsyncModulesDownloader netAsyncDownloader, bool enforceConsistency = true) { // Start by making sure we've downloaded everything. DownloadModules(modules, netAsyncDownloader); // Our upgrade involves removing everything that's currently installed, then // adding everything that needs installing (which may involve new mods to // satisfy dependencies). We always know the list passed in is what we need to // install, but we need to calculate what needs to be removed. var to_remove = new List<string>(); // Let's discover what we need to do with each module! foreach (CkanModule module in modules) { string ident = module.identifier; InstalledModule installed_mod = registry_manager.registry.InstalledModule(ident); if (installed_mod == null) { //Maybe ModuleNotInstalled ? if (registry_manager.registry.IsAutodetected(ident)) { throw new ModuleNotFoundKraken(ident, module.version.ToString(), String.Format("Can't upgrade {0} as it was not installed by CKAN. \r\n Please remove manually before trying to install it.", ident)); } User.RaiseMessage("Installing previously uninstalled mod {0}", ident); } else { // Module already installed. We'll need to remove it first. to_remove.Add(module.identifier); CkanModule installed = installed_mod.Module; if (installed.version.IsEqualTo(module.version)) { log.InfoFormat("{0} is already at the latest version, reinstalling", installed.identifier); } else if (installed.version.IsGreaterThan(module.version)) { log.WarnFormat("Downgrading {0} from {1} to {2}", ident, installed.version, module.version); } else { log.InfoFormat("Upgrading {0} to {1}", ident, module.version); } } } AddRemove( modules, to_remove, enforceConsistency ); }
/// <summary> /// Upgrades the mods listed to the latest versions for the user's KSP. /// Will *re-install* with warning even if an upgrade is not available. /// Throws ModuleNotFoundKraken if module is not installed, or not available. /// </summary> public void Upgrade(IEnumerable<string> identifiers, NetAsyncModulesDownloader netAsyncDownloader, bool enforceConsistency = true) { var options = new RelationshipResolverOptions(); // We do not wish to pull in any suggested or recommended mods. options.with_recommends = false; options.with_suggests = false; var resolver = new RelationshipResolver(identifiers.ToList(), options, registry_manager.registry, ksp.VersionCriteria()); Upgrade(resolver.ModList(), netAsyncDownloader, enforceConsistency); }
/// <summary> /// Installs all modules given a list of identifiers as a transaction. Resolves dependencies. /// This *will* save the registry at the end of operation. /// /// Propagates a BadMetadataKraken if our install metadata is bad. /// Propagates a FileExistsKraken if we were going to overwrite a file. /// Propagates a CancelledActionKraken if the user cancelled the install. /// </summary> // // TODO: Break this up into smaller pieces! It's huge! public void InstallList( ICollection<CkanModule> modules, RelationshipResolverOptions options, IDownloader downloader = null ) { var resolver = new RelationshipResolver(modules, options, registry_manager.registry, ksp.VersionCriteria()); var modsToInstall = resolver.ModList().ToList(); List<CkanModule> downloads = new List<CkanModule> (); // TODO: All this user-stuff should be happening in another method! // We should just be installing mods as a transaction. User.RaiseMessage("About to install...\r\n"); foreach (CkanModule module in modsToInstall) { if (!ksp.Cache.IsCachedZip(module.download)) { User.RaiseMessage(" * {0} {1}", module.name, module.version); downloads.Add(module); } else { User.RaiseMessage(" * {0} {1}(cached)", module.name, module.version); } } bool ok = User.RaiseYesNoDialog("\r\nContinue?"); if (!ok) { throw new CancelledActionKraken("User declined install list"); } User.RaiseMessage(String.Empty); // Just to look tidy. if (downloads.Count > 0) { if (downloader == null) { downloader = new NetAsyncModulesDownloader(User); } downloader.DownloadModules(ksp.Cache, downloads); } // We're about to install all our mods; so begin our transaction. using (TransactionScope transaction = CkanTransaction.CreateTransactionScope()) { for (int i = 0; i < modsToInstall.Count; i++) { int percent_complete = (i * 100) / modsToInstall.Count; User.RaiseProgress(String.Format("Installing mod \"{0}\"", modsToInstall[i]), percent_complete); Install(modsToInstall[i]); } User.RaiseProgress("Updating registry", 70); registry_manager.Save(!options.without_enforce_consistency); User.RaiseProgress("Commiting filesystem changes", 80); transaction.Complete(); } // We can scan GameData as a separate transaction. Installing the mods // leaves everything consistent, and this is just gravy. (And ScanGameData // acts as a Tx, anyway, so we don't need to provide our own.) User.RaiseProgress("Rescanning GameData", 90); if (!options.without_enforce_consistency) { ksp.ScanGameData(); } User.RaiseProgress("Done!\r\n", 100); }
// this probably needs to be refactored private void InstallMods(object sender, DoWorkEventArgs e) { installCanceled = false; ClearLog(); var opts = (KeyValuePair<ModChanges, RelationshipResolverOptions>) e.Argument; IRegistryQuerier registry = RegistryManager.Instance(manager.CurrentInstance).registry; ModuleInstaller installer = ModuleInstaller.GetInstance(CurrentInstance, GUI.user); // setup progress callback toInstall = new HashSet<string>(); var toUninstall = new HashSet<string>(); var toUpgrade = new HashSet<string>(); // First compose sets of what the user wants installed, upgraded, and removed. foreach (ModChange change in opts.Key) { switch (change.ChangeType) { case GUIModChangeType.Remove: toUninstall.Add(change.Mod.Identifier); break; case GUIModChangeType.Update: toUpgrade.Add(change.Mod.Identifier); break; case GUIModChangeType.Install: toInstall.Add(change.Mod.Identifier); break; } } // Now work on satisifying dependencies. var recommended = new Dictionary<string, List<string>>(); var suggested = new Dictionary<string, List<string>>(); foreach (var change in opts.Key) { if (change.ChangeType == GUIModChangeType.Install) { AddMod(change.Mod.ToModule().recommends, recommended, change.Mod.Identifier, registry); AddMod(change.Mod.ToModule().suggests, suggested, change.Mod.Identifier, registry); } } ShowSelection(recommended); ShowSelection(suggested, true); tabController.HideTab("ChooseRecommendedModsTabPage"); if (installCanceled) { tabController.HideTab("WaitTabPage"); tabController.ShowTab("ManageModsTabPage"); e.Result = new KeyValuePair<bool, ModChanges>(false, opts.Key); return; } // Now let's make all our changes. tabController.RenameTab("WaitTabPage", "Status log"); tabController.ShowTab("WaitTabPage"); tabController.SetTabLock(true); var downloader = new NetAsyncModulesDownloader(GUI.user); cancelCallback = () => { downloader.CancelDownload(); installCanceled = true; }; //Transaction is needed here to revert changes when an installation is cancelled //TODO: Cancellation should be handelt in the ModuleInstaller using (var transaction = CkanTransaction.CreateTransactionScope()) { //Set the result to false/failed in case we return e.Result = new KeyValuePair<bool, ModChanges>(false, opts.Key); SetDescription("Uninstalling selected mods"); if (!WasSuccessful(() => installer.UninstallList(toUninstall))) return; if (installCanceled) return; SetDescription("Updating selected mods"); if (!WasSuccessful(() => installer.Upgrade(toUpgrade, downloader))) return; if (installCanceled) return; // TODO: We should be able to resolve all our provisioning conflicts // before we start installing anything. CKAN.SanityChecker can be used to // pre-check if our changes are going to be consistent. bool resolvedAllProvidedMods = false; while (!resolvedAllProvidedMods) { if (installCanceled) { e.Result = new KeyValuePair<bool, ModChanges>(false, opts.Key); return; } 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<bool, ModChanges>(false, opts.Key); return; } resolvedAllProvidedMods = true; } if (!installCanceled) { transaction.Complete(); } } e.Result = new KeyValuePair<bool, ModChanges>(!installCanceled, opts.Key); }
public void InstallAndSortByCompat_WithAnyCompat_NoCrash() { /* * // An exception would be thrown at the bottom of this. * var main = new Main(null, new GUIUser(), false); * main.Manager = _manager; * // First sort by name * main.configuration.SortByColumnIndex = 2; * // Now sort by version * main.configuration.SortByColumnIndex = 6; * main.MarkModForInstall("kOS"); * * // Make sure we have one requested change * var changeList = main.mainModList.ComputeUserChangeSet() * .Select((change) => change.Mod.ToCkanModule()).ToList(); * * // Do the install * new ModuleInstaller(_instance.KSP, main.currentUser).InstallList( * changeList, * new RelationshipResolverOptions(), * new NetAsyncModulesDownloader(main.currentUser) * ); */ // Arrange DisposableKSP instance = new DisposableKSP(); RegistryManager registryManager = RegistryManager.Instance(instance.KSP); Registry registry = Registry.Empty(); FakeConfiguration config = new FakeConfiguration(instance.KSP, instance.KSP.Name); GameInstanceManager manager = new GameInstanceManager(new NullUser(), config); // A module with a ksp_version of "any" to repro our issue CkanModule anyVersionModule = TestData.DogeCoinFlag_101_module(); ModList modList = new ModList(null); DataGridView listGui = new DataGridView(); CKAN.ModuleInstaller installer = new CKAN.ModuleInstaller(instance.KSP, manager.Cache, manager.User); NetAsyncModulesDownloader downloader = new NetAsyncModulesDownloader(manager.User, manager.Cache); // Act // Install module and set it as pre-installed manager.Cache.Store(TestData.DogeCoinFlag_101_module(), TestData.DogeCoinFlagZip()); registry.RegisterModule(anyVersionModule, new string[] { }, instance.KSP, false); registry.AddAvailable(anyVersionModule); HashSet <string> possibleConfigOnlyDirs = null; installer.InstallList( new List <CkanModule> { anyVersionModule }, new RelationshipResolverOptions(), registryManager, ref possibleConfigOnlyDirs, downloader ); // This module is not for "any" version, // to provide another to sort against registry.AddAvailable(TestData.kOS_014_module()); // TODO: Refactor the column header code to allow mocking of the GUI without creating columns const int numCheckboxCols = 4; const int numTextCols = 10; listGui.Columns.AddRange( Enumerable.Range(1, numCheckboxCols) .Select(i => (DataGridViewColumn) new DataGridViewCheckBoxColumn()) .Concat(Enumerable.Range(1, numTextCols) .Select(i => new DataGridViewTextBoxColumn())) .ToArray()); // Assert (and Act a bit more) Assert.IsNotNull(instance.KSP); Assert.IsNotNull(manager); Assert.IsNotNull(modList); var modules = registry.available_modules .Select(mod => new GUIMod(mod.Value.Latest(), registry, instance.KSP.VersionCriteria())) .ToList(); listGui.Rows.AddRange(modList.ConstructModList(modules, null).ToArray()); // The header row adds one to the count Assert.AreEqual(modules.Count + 1, listGui.Rows.Count); // Sort by game compatibility, this is the fuse-lighting listGui.Sort(listGui.Columns[8], ListSortDirection.Descending); // Mark the mod for install, after completion we will get an exception var otherModule = modules.First(mod => mod.Identifier.Contains("kOS")); otherModule.IsInstallChecked = true; Assert.IsTrue(otherModule.IsInstallChecked); Assert.IsFalse(otherModule.IsInstalled); Assert.DoesNotThrow(() => { // Install the "other" module installer.InstallList( modList.ComputeUserChangeSet(null).Select(change => change.Mod).ToList(), new RelationshipResolverOptions(), registryManager, ref possibleConfigOnlyDirs, downloader ); // Now we need to sort // Make sure refreshing the GUI state does not throw a NullReferenceException listGui.Refresh(); }); instance.Dispose(); manager.Dispose(); config.Dispose(); }