/// <summary> /// Clears the registry of DLL data, and refreshes it by scanning GameData. /// This operates as a transaction. /// This *saves* the registry upon completion. /// </summary> // TODO: This would likely be better in the Registry class itself. public void ScanGameData() { using (CkanTransaction tx = new CkanTransaction()) { Registry.ClearDlls(); // TODO: It would be great to optimise this to skip .git directories and the like. // Yes, I keep my GameData in git. // Alas, EnumerateFiles is *case-sensitive* in its pattern, which causes // DLL files to be missed under Linux; we have to pick .dll, .DLL, or scanning // GameData *twice*. // // The least evil is to walk it once, and filter it ourselves. IEnumerable <string> files = Directory.EnumerateFiles( GameData(), "*", SearchOption.AllDirectories ); files = files.Where(file => Regex.IsMatch(file, @"\.dll$", RegexOptions.IgnoreCase)); foreach (string dll in files.Select(KSPPathUtils.NormalizePath)) { Registry.RegisterDll(this, dll); } tx.Complete(); } RegistryManager.Save(); }
/// <summary> /// Install our mod from the filename supplied. /// If no file is supplied, we will check the cache or throw FileNotFoundKraken. /// Does *not* resolve dependencies; this actually does the heavy listing. /// Does *not* save the registry. /// Do *not* call this directly, use InstallList() instead. /// /// Propagates a BadMetadataKraken if our install metadata is bad. /// Propagates a FileExistsKraken if we were going to overwrite a file. /// Throws a FileNotFoundKraken if we can't find the downloaded module. /// /// </summary> // // TODO: The name of this and InstallModule() need to be made more distinctive. private void Install(CkanModule module, string filename = null) { Version version = registry_manager.registry.InstalledVersion(module.identifier); // TODO: This really should be handled by higher-up code. if (version != null) { User.RaiseMessage(" {0} {1} already installed, skipped", module.identifier, version); return; } // Find our in the cache if we don't already have it. filename = filename ?? Cache.GetCachedZip(module.download); // If we *still* don't have a file, then kraken bitterly. if (filename == null) { throw new FileNotFoundKraken( null, String.Format("Trying to install {0}, but it's not downloaded", module) ); } // We'll need our registry to record which files we've installed. Registry registry = registry_manager.registry; using (var transaction = new CkanTransaction()) { // Install all the things! IEnumerable <string> files = InstallModule(module, filename); // Register our module and its files. registry.RegisterModule(module, files, ksp); // Finish our transaction, but *don't* save the registry; we may be in an // intermediate, inconsistent state. // This is fine from a transaction standpoint, as we may not have an enclosing // transaction, and if we do, they can always roll us back. transaction.Complete(); } // Fire our callback that we've installed a module, if we have one. if (onReportModInstalled != null) { onReportModInstalled(module); } }
/// <summary> /// Uninstalls all the mods provided, including things which depend upon them. /// This *DOES* save the registry. /// Preferred over Uninstall. /// </summary> public void UninstallList(IEnumerable <string> mods) { // Pre-check, have they even asked for things which are installed? foreach (string mod in mods.Where(mod => registry_manager.registry.InstalledModule(mod) == null)) { throw new ModNotInstalledKraken(mod); } // Find all the things which need uninstalling. IEnumerable <string> goners = registry_manager.registry.FindReverseDependencies(mods); User.RaiseMessage("About to remove:\n"); foreach (string mod in goners) { User.RaiseMessage(" * {0}", mod); } bool ok = User.RaiseYesNoDialog("\nContinue?"); if (!ok) { User.RaiseMessage("Mod removal aborted at user request."); return; } using (var transaction = new CkanTransaction()) { foreach (string mod in goners) { User.RaiseMessage("Removing {0}...", mod); Uninstall(mod); } registry_manager.Save(); transaction.Complete(); } User.RaiseMessage("Done!"); }
/// <summary> /// Adds and removes the listed modules as a single transaction. /// No relationships will be processed. /// This *will* save the registry. /// </summary> /// <param name="add">Add.</param> /// <param name="remove">Remove.</param> public void AddRemove(IEnumerable <CkanModule> add = null, IEnumerable <string> remove = null) { // TODO: We should do a consistency check up-front, rather than relying // upon our registry catching inconsistencies at the end. // TODO: Download our files. using (var tx = new CkanTransaction()) { foreach (string identifier in remove) { Uninstall(identifier); } foreach (CkanModule module in add) { Install(module); } registry_manager.Save(); tx.Complete(); } }
private void InstallMods(object sender, DoWorkEventArgs e) // this probably needs to be refactored { installCanceled = false; ClearLog(); var opts = (KeyValuePair <List <KeyValuePair <CkanModule, GUIModChangeType> >, RelationshipResolverOptions>)e.Argument; 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 (KeyValuePair <CkanModule, GUIModChangeType> change in opts.Key) { switch (change.Value) { case GUIModChangeType.Remove: toUninstall.Add(change.Key.identifier); break; case GUIModChangeType.Update: toUpgrade.Add(change.Key.identifier); break; case GUIModChangeType.Install: toInstall.Add(change.Key.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.Value == GUIModChangeType.Install) { if (change.Key.recommends != null) { foreach (RelationshipDescriptor mod in change.Key.recommends) { try { // if the mod is available for the current KSP version _and_ // the mod is not installed _and_ // the mod is not already in the install list if ( RegistryManager.Instance(manager.CurrentInstance) .registry.LatestAvailable(mod.name, manager.CurrentInstance.Version()) != null && !RegistryManager.Instance(manager.CurrentInstance) .registry.IsInstalled(mod.name) && !toInstall.Contains(mod.name)) { // add it to the list of recommended mods we display to the user if (recommended.ContainsKey(mod.name)) { recommended[mod.name].Add(change.Key.identifier); } else { recommended.Add(mod.name, new List <string> { change.Key.identifier }); } } } // XXX - Don't ignore all krakens! Those things are important! catch (Kraken) { } } } if (change.Key.suggests != null) { foreach (RelationshipDescriptor mod in change.Key.suggests) { try { if ( RegistryManager.Instance(manager.CurrentInstance).registry.LatestAvailable(mod.name, manager.CurrentInstance.Version()) != null && !RegistryManager.Instance(manager.CurrentInstance).registry.IsInstalled(mod.name) && !toInstall.Contains(mod.name)) { if (suggested.ContainsKey(mod.name)) { suggested[mod.name].Add(change.Key.identifier); } else { suggested.Add(mod.name, new List <string> { change.Key.identifier }); } } } // XXX - Don't ignore all krakens! Those things are important! catch (Kraken) { } } } } } // If we're going to install something anyway, then don't list it in the // recommended list, since they can't de-select it anyway. foreach (var item in toInstall) { recommended.Remove(item); } // If there are any mods that would be recommended, prompt the user to make // selections. if (recommended.Any()) { Util.Invoke(this, () => UpdateRecommendedDialog(recommended)); m_TabController.ShowTab("ChooseRecommendedModsTabPage", 3); m_TabController.RenameTab("ChooseRecommendedModsTabPage", "Choose recommended mods"); m_TabController.SetTabLock(true); lock (this) { Monitor.Wait(this); } m_TabController.SetTabLock(false); } m_TabController.HideTab("ChooseRecommendedModsTabPage"); // And now on to suggestions. Again, we don't show anything that's scheduled to // be installed on our suggest list. foreach (var item in toInstall) { suggested.Remove(item); } if (suggested.Any()) { Util.Invoke(this, () => UpdateRecommendedDialog(suggested, true)); m_TabController.ShowTab("ChooseRecommendedModsTabPage", 3); m_TabController.RenameTab("ChooseRecommendedModsTabPage", "Choose suggested mods"); m_TabController.SetTabLock(true); lock (this) { Monitor.Wait(this); } m_TabController.SetTabLock(false); } m_TabController.HideTab("ChooseRecommendedModsTabPage"); if (installCanceled) { m_TabController.HideTab("WaitTabPage"); m_TabController.ShowTab("ManageModsTabPage"); e.Result = new KeyValuePair <bool, List <KeyValuePair <CkanModule, GUIModChangeType> > >(false, opts.Key); return; } // Now let's make all our changes. m_TabController.RenameTab("WaitTabPage", "Installing mods"); m_TabController.ShowTab("WaitTabPage"); m_TabController.SetTabLock(true); using (var transaction = new CkanTransaction()) { var downloader = new NetAsyncDownloader(GUI.user); cancelCallback = () => { downloader.CancelDownload(); installCanceled = true; }; SetDescription("Uninstalling selected mods"); installer.UninstallList(toUninstall); if (installCanceled) { return; } SetDescription("Updating selected mods"); installer.Upgrade(toUpgrade, downloader); // 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, List <KeyValuePair <CkanModule, GUIModChangeType> > >(false, 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 <bool, List <KeyValuePair <CkanModule, GUIModChangeType> > >(false, opts.Key); return; } resolvedAllProvidedMods = true; } catch (TooManyModsProvideKraken tooManyProvides) { 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 <bool, List <KeyValuePair <CkanModule, GUIModChangeType> > >(false, opts.Key); return; } m_TabController.ShowTab("WaitTabPage"); } } if (!installCanceled) { transaction.Complete(); } } e.Result = new KeyValuePair <bool, List <KeyValuePair <CkanModule, GUIModChangeType> > >(true, opts.Key); }
/// <summary> /// Uninstall the module provided. For internal use only. /// Use UninstallList for user queries, it also does dependency handling. /// This does *NOT* save the registry. /// </summary> private void Uninstall(string modName) { using (var transaction = new CkanTransaction()) { InstalledModule mod = registry_manager.registry.InstalledModule(modName); if (mod == null) { log.ErrorFormat("Trying to uninstall {0} but it's not installed", modName); throw new ModNotInstalledKraken(modName); } // Walk our registry to find all files for this mod. IEnumerable <string> files = mod.Files; var directoriesToDelete = new HashSet <string>(); foreach (string file in files) { string path = ksp.ToAbsoluteGameDir(file); try { FileAttributes attr = File.GetAttributes(path); if ((attr & FileAttributes.Directory) == FileAttributes.Directory) { directoriesToDelete.Add(path); } else { log.InfoFormat("Removing {0}", file); file_transaction.Delete(path); } } catch (Exception ex) { // XXX: This is terrible, we're catching all exceptions. log.ErrorFormat("Failure in locating file {0} : {1}", path, ex.Message); } } // Remove from registry. registry_manager.registry.DeregisterModule(ksp, modName); // Sort our directories from longest to shortest, to make sure we remove child directories // before parents. GH #78. foreach (string directory in directoriesToDelete.OrderBy(dir => dir.Length).Reverse()) { if (!Directory.EnumerateFileSystemEntries(directory).Any()) { // We *don't* use our file_transaction to delete files here, because // it fails if the system's temp directory is on a different device // to KSP. However we *can* safely delete it now we know it's empty, // because the TxFileMgr *will* put it back if there's a file inside that // needs it. // // This works around GH #251. // The filesystem boundry bug is described in https://transactionalfilemgr.codeplex.com/workitem/20 log.InfoFormat("Removing {0}", directory); Directory.Delete(directory); } else { User.RaiseMessage("Not removing directory {0}, it's not empty", directory); } } transaction.Complete(); } }
/// <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( List <string> modules, RelationshipResolverOptions options, NetAsyncDownloader downloader = null ) { var resolver = new RelationshipResolver(modules, options, registry_manager.registry, ksp.Version()); List <CkanModule> modsToInstall = resolver.ModList(); 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...\n"); foreach (CkanModule module in modsToInstall) { if (!ksp.Cache.IsCachedZip(module.download)) { User.RaiseMessage(" * {0}", module); downloads.Add(module); } else { User.RaiseMessage(" * {0} (cached)", module); } } bool ok = User.RaiseYesNoDialog("\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 NetAsyncDownloader(User); } downloader.DownloadModules(ksp.Cache, downloads); } // We're about to install all our mods; so begin our transaction. using (CkanTransaction transaction = new CkanTransaction()) { 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(); 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); ksp.ScanGameData(); User.RaiseProgress("Done!", 100); }