From 13792f1117e5925853483a6d0dbdf1d2c4d01159 Mon Sep 17 00:00:00 2001 From: Paul Hebble Date: Sat, 23 Dec 2017 20:50:24 -0600 Subject: [PATCH] Use shared installer code in GUI --- Core/ModuleInstaller.cs | 27 +--- Core/Net/AutoUpdate.cs | 2 +- Core/Net/NetAsyncDownloader.cs | 2 +- GUI/MainInstall.cs | 281 ++++++++++++--------------------- Netkan/CmdLineOptions.cs | 2 +- 5 files changed, 112 insertions(+), 202 deletions(-) diff --git a/Core/ModuleInstaller.cs b/Core/ModuleInstaller.cs index e258b0759c..c81cb74795 100644 --- a/Core/ModuleInstaller.cs +++ b/Core/ModuleInstaller.cs @@ -190,12 +190,12 @@ public void InstallList( { if (!ksp.Cache.IsCachedZip(module.download)) { - User.RaiseMessage(" * {0} {1}", module.name, module.version); + User.RaiseMessage(" * {0} {1} ({2})", module.name, module.version, module.download.Host); downloads.Add(module); } else { - User.RaiseMessage(" * {0} {1}(cached)", module.name, module.version); + User.RaiseMessage(" * {0} {1} (cached)", module.name, module.version); } } @@ -255,23 +255,6 @@ public void InstallList( User.RaiseProgress("Done!\r\n", 100); } - public ModuleResolution ResolveModules(IEnumerable modules, RelationshipResolverOptions options) - { - var resolver = new RelationshipResolver(modules, options, registry_manager.registry, ksp.VersionCriteria()); - return new ModuleResolution(resolver.ModList(), m => ksp.Cache.IsCachedZip(m.download)); - } - - public void EnsureCache(List modules, IDownloader downloader = null) - { - if (!modules.Any()) - { - return; - } - - downloader = downloader ?? new NetAsyncModulesDownloader(User); - downloader.DownloadModules(ksp.Cache, modules); - } - public void InstallList(ModuleResolution modules, RelationshipResolverOptions options) { // We're about to install all our mods; so begin our transaction. @@ -1035,7 +1018,7 @@ public void AddRemove(IEnumerable add = null, IEnumerable re /// Will *re-install* with warning even if an upgrade is not available. /// Throws ModuleNotFoundKraken if module is not installed, or not available. /// - public void Upgrade(IEnumerable identifiers, NetAsyncModulesDownloader netAsyncDownloader, bool enforceConsistency = true) + public void Upgrade(IEnumerable identifiers, IDownloader netAsyncDownloader, bool enforceConsistency = true) { var options = new RelationshipResolverOptions(); @@ -1052,7 +1035,7 @@ public void Upgrade(IEnumerable identifiers, NetAsyncModulesDownloader n /// Will *re-install* or *downgrade* (with a warning) as well as upgrade. /// Throws ModuleNotFoundKraken if a module is not installed. /// - public void Upgrade(IEnumerable modules, NetAsyncModulesDownloader netAsyncDownloader, bool enforceConsistency = true) + public void Upgrade(IEnumerable modules, IDownloader netAsyncDownloader, bool enforceConsistency = true) { // Start by making sure we've downloaded everything. DownloadModules(modules, netAsyncDownloader); @@ -1112,7 +1095,7 @@ public void Upgrade(IEnumerable modules, NetAsyncModulesDownloader n /// /// Makes sure all the specified mods are downloaded. /// - private void DownloadModules(IEnumerable mods, NetAsyncModulesDownloader downloader) + private void DownloadModules(IEnumerable mods, IDownloader downloader) { List downloads = mods.Where(module => !ksp.Cache.IsCachedZip(module.download)).ToList(); diff --git a/Core/Net/AutoUpdate.cs b/Core/Net/AutoUpdate.cs index c6d0e46344..cda42e7bfc 100644 --- a/Core/Net/AutoUpdate.cs +++ b/Core/Net/AutoUpdate.cs @@ -193,7 +193,7 @@ internal Tuple RetrieveUrl(dynamic response, int whichOne) internal dynamic MakeRequest(Uri url) { var web = new WebClient(); - web.Headers.Add("user-agent", Net.UserAgentString); + web.Headers.Add("User-Agent", Net.UserAgentString); try { diff --git a/Core/Net/NetAsyncDownloader.cs b/Core/Net/NetAsyncDownloader.cs index 6f560b6e96..fd5b53505c 100644 --- a/Core/Net/NetAsyncDownloader.cs +++ b/Core/Net/NetAsyncDownloader.cs @@ -39,7 +39,7 @@ public NetAsyncDownloaderDownloadPart(Uri url, long expectedSize, string path = size = expectedSize; lastProgressUpdateTime = DateTime.Now; - agent.Headers.Add("user-agent", Net.UserAgentString); + agent.Headers.Add("User-Agent", Net.UserAgentString); } } diff --git a/GUI/MainInstall.cs b/GUI/MainInstall.cs index c01227dbd5..996774b3cf 100644 --- a/GUI/MainInstall.cs +++ b/GUI/MainInstall.cs @@ -14,7 +14,6 @@ public partial class Main { private BackgroundWorker installWorker; - // used to signal the install worker that the user canceled the install process // this may happen on the recommended/suggested mods dialogs private volatile bool installCanceled; @@ -28,16 +27,16 @@ private void InstallMods(object sender, DoWorkEventArgs e) // this probably need ClearLog(); - var opts = - (KeyValuePair) e.Argument; + var opts = (KeyValuePair) e.Argument; - IRegistryQuerier registry = RegistryManager.Instance(manager.CurrentInstance).registry; - ModuleInstaller installer = ModuleInstaller.GetInstance(CurrentInstance, GUI.user); + IRegistryQuerier registry = RegistryManager.Instance(manager.CurrentInstance).registry; + ModuleInstaller installer = ModuleInstaller.GetInstance(CurrentInstance, GUI.user); + installer.onReportModInstalled += OnModInstalled; // setup progress callback - toInstall = new HashSet(); + toInstall = new HashSet(); var toUninstall = new HashSet(); - var toUpgrade = new HashSet(); + var toUpgrade = new HashSet(); // First compose sets of what the user wants installed, upgraded, and removed. foreach (ModChange change in opts.Key) @@ -59,14 +58,14 @@ private void InstallMods(object sender, DoWorkEventArgs e) // this probably need // Now work on satisifying dependencies. var recommended = new Dictionary>(); - var suggested = new Dictionary>(); + var suggested = new Dictionary>(); 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); + AddMod(change.Mod.ToModule().suggests, suggested, change.Mod.Identifier, registry); } } @@ -89,86 +88,121 @@ private void InstallMods(object sender, DoWorkEventArgs e) // this probably need tabController.ShowTab("WaitTabPage"); tabController.SetTabLock(true); - - var downloader = new NetAsyncModulesDownloader(GUI.user); + IDownloader downloader = new NetAsyncModulesDownloader(GUI.user); cancelCallback = () => { downloader.CancelDownload(); installCanceled = true; }; - GUI.user.RaiseMessage("About to install...\r\n"); - - // FIXME: here we should heat up the cache with any mods we're going to install, because - // when this transaction types out it messes up everything else. - var resolvedMods = installer.ResolveModules(toInstall, opts.Value); - - foreach (var module in resolvedMods.CachedModules) - { - GUI.user.RaiseMessage(" * {0} {1}(cached)", module.name, module.version); - } - - foreach (var module in resolvedMods.UncachedModules) - { - GUI.user.RaiseMessage(" * {0} {1}", module.name, module.version); - } - - if (!GUI.user.RaiseYesNoDialog("\r\nContinue?")) - { - throw new CancelledActionKraken("User declined install list"); - } - - GUI.user.RaiseMessage(String.Empty); // Just to look tidy. - - installer.EnsureCache(resolvedMods.UncachedModules); - - //Transaction is needed here to revert changes when an installation is cancelled - //TODO: Cancellation should be handled in the ModuleInstaller - using (var transaction = CkanTransaction.CreateTransactionScope()) + bool resolvedAllProvidedMods = false; + while (!resolvedAllProvidedMods) { - - //Set the result to false/failed in case we return - e.Result = new KeyValuePair(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) + try { - if (installCanceled) + e.Result = new KeyValuePair(false, opts.Key); + if (!installCanceled && toUninstall.Count > 0) { - e.Result = new KeyValuePair(false, opts.Key); - return; + installer.UninstallList(toUninstall); } - var ret = InstallList(resolvedMods, opts.Value); - if (!ret) + if (!installCanceled && toUpgrade.Count > 0) + { + installer.Upgrade(toUpgrade, downloader); + } + if (!installCanceled && toInstall.Count > 0) + { + installer.InstallList(toInstall, opts.Value, downloader); + } + e.Result = new KeyValuePair(!installCanceled, opts.Key); + if (installCanceled) { - // install failed for some reason, error message is already displayed to the user - e.Result = new KeyValuePair(false, opts.Key); return; } resolvedAllProvidedMods = true; } + catch (DependencyNotSatisfiedKraken ex) + { + GUI.user.RaiseMessage( + "{0} requires {1} but it is not listed in the index, or not available for your version of KSP.", + ex.parent, ex.module); + return; + } + catch (ModuleNotFoundKraken ex) + { + GUI.user.RaiseMessage( + "Module {0} required but it is not listed in the index, or not available for your version of KSP.", + ex.module); + return; + } + catch (BadMetadataKraken ex) + { + GUI.user.RaiseMessage("Bad metadata detected for module {0}: {1}", ex.module, ex.Message); + return; + } + catch (FileExistsKraken ex) + { + if (ex.owningModule != null) + { + GUI.user.RaiseMessage( + "\r\nOh no! We tried to overwrite a file owned by another mod!\r\n" + + "Please try a `ckan update` and try again.\r\n\r\n" + + "If this problem re-occurs, then it maybe a packaging bug.\r\n" + + "Please report it at:\r\n\r\n" + + "https://github.com/KSP-CKAN/NetKAN/issues/new\r\n\r\n" + + "Please including the following information in your report:\r\n\r\n" + + "File : {0}\r\n" + + "Installing Mod : {1}\r\n" + + "Owning Mod : {2}\r\n" + + "CKAN Version : {3}\r\n", + ex.filename, ex.installingModule, ex.owningModule, + Meta.GetVersion() + ); + } + else + { + GUI.user.RaiseMessage( + "\r\n\r\nOh no!\r\n\r\n" + + "It looks like you're trying to install a mod which is already installed,\r\n" + + "or which conflicts with another mod which is already installed.\r\n\r\n" + + "As a safety feature, the CKAN will *never* overwrite or alter a file\r\n" + + "that it did not install itself.\r\n\r\n" + + "If you wish to install {0} via the CKAN,\r\n" + + "then please manually uninstall the mod which owns:\r\n\r\n" + + "{1}\r\n\r\n" + "and try again.\r\n", + ex.installingModule, ex.filename + ); + } - if (!installCanceled) + GUI.user.RaiseMessage("Your GameData has been returned to its original state.\r\n"); + return; + } + catch (InconsistentKraken ex) + { + // The prettiest Kraken formats itself for us. + GUI.user.RaiseMessage(ex.InconsistenciesPretty); + return; + } + catch (CancelledActionKraken) { - transaction.Complete(); + return; + } + catch (MissingCertificateKraken kraken) + { + // Another very pretty kraken. + GUI.user.RaiseMessage(kraken.ToString()); + return; + } + catch (DownloadErrorsKraken) + { + // User notified in InstallList + return; + } + catch (DirectoryNotFoundKraken kraken) + { + GUI.user.RaiseMessage("\r\n{0}", kraken.Message); + return; } } - - e.Result = new KeyValuePair(!installCanceled, opts.Key); } private void AddMod(IEnumerable relations, Dictionary> chooseAble, @@ -234,113 +268,6 @@ private void ShowSelection(Dictionary> selectable, bool sug } } - /// - /// Helper function to wrap around calls to ModuleInstaller. - /// Handles some of the possible krakens and displays user friendly messages for them. - /// - private static bool WasSuccessful(Action action) - { - try - { - action(); - } - catch (DependencyNotSatisfiedKraken ex) - { - GUI.user.RaiseMessage( - "{0} requires {1} but it is not listed in the index, or not available for your version of KSP.", - ex.parent, ex.module); - return false; - } - catch (ModuleNotFoundKraken ex) - { - GUI.user.RaiseMessage( - "Module {0} required but it is not listed in the index, or not available for your version of KSP.", - ex.module); - return false; - } - catch (BadMetadataKraken ex) - { - GUI.user.RaiseMessage("Bad metadata detected for module {0}: {1}", ex.module, ex.Message); - return false; - } - catch (FileExistsKraken ex) - { - if (ex.owningModule != null) - { - GUI.user.RaiseMessage( - "\r\nOh no! We tried to overwrite a file owned by another mod!\r\n" + - "Please try a `ckan update` and try again.\r\n\r\n" + - "If this problem re-occurs, then it maybe a packaging bug.\r\n" + - "Please report it at:\r\n\r\n" + - "https://github.com/KSP-CKAN/NetKAN/issues/new\r\n\r\n" + - "Please including the following information in your report:\r\n\r\n" + - "File : {0}\r\n" + - "Installing Mod : {1}\r\n" + - "Owning Mod : {2}\r\n" + - "CKAN Version : {3}\r\n", - ex.filename, ex.installingModule, ex.owningModule, - Meta.GetVersion() - ); - } - else - { - GUI.user.RaiseMessage( - "\r\n\r\nOh no!\r\n\r\n" + - "It looks like you're trying to install a mod which is already installed,\r\n" + - "or which conflicts with another mod which is already installed.\r\n\r\n" + - "As a safety feature, the CKAN will *never* overwrite or alter a file\r\n" + - "that it did not install itself.\r\n\r\n" + - "If you wish to install {0} via the CKAN,\r\n" + - "then please manually uninstall the mod which owns:\r\n\r\n" + - "{1}\r\n\r\n" + "and try again.\r\n", - ex.installingModule, ex.filename - ); - } - - GUI.user.RaiseMessage("Your GameData has been returned to its original state.\r\n"); - return false; - } - catch (InconsistentKraken ex) - { - // The prettiest Kraken formats itself for us. - GUI.user.RaiseMessage(ex.InconsistenciesPretty); - return false; - } - catch (CancelledActionKraken) - { - return false; - } - catch (MissingCertificateKraken kraken) - { - // Another very pretty kraken. - GUI.user.RaiseMessage(kraken.ToString()); - return false; - } - catch (DownloadErrorsKraken) - { - // User notified in InstallList - return false; - } - catch (DirectoryNotFoundKraken kraken) - { - GUI.user.RaiseMessage("\r\n{0}", kraken.Message); - return false; - } - return true; - } - private bool InstallList(ModuleResolution modules, RelationshipResolverOptions options) - { - if (toInstall.Any()) - { - // actual magic happens here, we run the installer with our mod list - var module_installer = ModuleInstaller.GetInstance(manager.CurrentInstance, GUI.user); - module_installer.onReportModInstalled = OnModInstalled; - return WasSuccessful(() => module_installer.InstallList(modules, options)); - } - - return true; - } - private void OnModInstalled(CkanModule mod) { AddStatusMessage("Module \"{0}\" successfully installed", mod.name); diff --git a/Netkan/CmdLineOptions.cs b/Netkan/CmdLineOptions.cs index acf55d7b5c..96262b4491 100644 --- a/Netkan/CmdLineOptions.cs +++ b/Netkan/CmdLineOptions.cs @@ -25,7 +25,7 @@ internal class CmdLineOptions [Option("github-token", HelpText = "GitHub OAuth token for API access")] public string GitHubToken { get; set; } - [Option("net-useragent", DefaultValue = null, HelpText = "Set the default user-agent string for HTTP requests")] + [Option("net-useragent", DefaultValue = null, HelpText = "Set the default User-Agent string for HTTP requests")] public string NetUserAgent { get; set; } [Option("prerelease", HelpText = "Index GitHub prereleases")]