/// <summary> /// Find installed modules that have different metadata in their equivalent available module /// </summary> /// <param name="registry">Registry to scan</param> /// <returns> /// List of CkanModules that are available and have changed metadata /// </returns> private static List <CkanModule> GetChangedInstalledModules(Registry registry) { List <CkanModule> metadataChanges = new List <CkanModule>(); foreach (InstalledModule installedModule in registry.InstalledModules) { string identifier = installedModule.identifier; ModuleVersion installedVersion = registry.InstalledVersion(identifier); if (!(registry.available_modules.ContainsKey(identifier))) { log.InfoFormat("UpdateRegistry, module {0}, version {1} not in registry", identifier, installedVersion); continue; } if (!registry.available_modules[identifier].module_version.ContainsKey(installedVersion)) { continue; } // if the mod is installed and the metadata is different we have to reinstall it CkanModule metadata = registry.available_modules[identifier].module_version[installedVersion]; CkanModule oldMetadata = registry.InstalledModule(identifier).Module; if (!MetadataEquals(metadata, oldMetadata)) { metadataChanges.Add(registry.available_modules[identifier].module_version[installedVersion]); } } return(metadataChanges); }
/// <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<KeyValuePair<GUIMod, GUIModChangeType>>> ComputeChangeSetFromModList( Registry registry, HashSet<KeyValuePair<GUIMod, GUIModChangeType>> 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.Value) { case GUIModChangeType.None: break; case GUIModChangeType.Install: modules_to_install.Add(change.Key.ToCkanModule()); break; case GUIModChangeType.Remove: modules_to_remove.Add(change.Key); 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 modules_to_remove. Select(mod => installer.FindReverseDependencies(mod.identifier)). SelectMany(reverse_dependencies => reverse_dependencies)) { //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 KeyValuePair<GUIMod, GUIModChangeType>( new GUIMod(module_by_version, registry, version), GUIModChangeType.Remove)); } //May throw InconsistentKraken var resolver = new RelationshipResolver(options, registry, version); resolver.RemoveModsFromInstalledList( changeSet.Where(change => change.Value.Equals(GUIModChangeType.Remove)).Select(m => m.Key.ToModule())); resolver.AddModulesToInstall(modules_to_install.ToList()); changeSet.UnionWith( resolver.ModList() .Select( mod => new KeyValuePair<GUIMod, GUIModChangeType>(new GUIMod(mod, registry, version), GUIModChangeType.Install))); return changeSet; }
/// <summary> /// Updates the supplied registry from the URL given. /// This does not *save* the registry. For that, you probably want Repo.Update /// </summary> internal static void UpdateRegistry(Uri repo, Registry registry, KSP ksp, IUser user, Boolean clear = true) { // Use this opportunity to also update the build mappings... kind of hacky ServiceLocator.Container.Resolve <IKspBuildMap>().Refresh(); log.InfoFormat("Downloading {0}", repo); string repo_file = String.Empty; try { repo_file = Net.Download(repo); } catch (System.Net.WebException) { user.RaiseMessage("Connection to {0} could not be established.", repo); return; } // Clear our list of known modules. if (clear) { registry.ClearAvailable(); } // Check the filetype. FileType type = FileIdentifier.IdentifyFile(repo_file); switch (type) { case FileType.TarGz: UpdateRegistryFromTarGz(repo_file, registry); break; case FileType.Zip: UpdateRegistryFromZip(repo_file, registry); break; default: break; } List <CkanModule> metadataChanges = new List <CkanModule>(); foreach (var installedModule in registry.InstalledModules) { var identifier = installedModule.identifier; var installedVersion = registry.InstalledVersion(identifier); if (!(registry.available_modules.ContainsKey(identifier))) { log.InfoFormat("UpdateRegistry, module {0}, version {1} not in repository ({2})", identifier, installedVersion, repo); continue; } if (!registry.available_modules[identifier].module_version.ContainsKey(installedVersion)) { continue; } // if the mod is installed and the metadata is different we have to reinstall it var metadata = registry.available_modules[identifier].module_version[installedVersion]; var oldMetadata = registry.InstalledModule(identifier).Module; bool same = true; if ((metadata.install == null) != (oldMetadata.install == null) || (metadata.install != null && metadata.install.Length != oldMetadata.install.Length)) { same = false; } else { if (metadata.install != null) { for (int i = 0; i < metadata.install.Length; i++) { if (metadata.install[i].file != oldMetadata.install[i].file) { same = false; break; } if (metadata.install[i].install_to != oldMetadata.install[i].install_to) { same = false; break; } if (metadata.install[i].@as != oldMetadata.install[i].@as) { same = false; break; } if ((metadata.install[i].filter == null) != (oldMetadata.install[i].filter == null)) { same = false; break; } if (metadata.install[i].filter != null) { if (!metadata.install[i].filter.SequenceEqual(oldMetadata.install[i].filter)) { same = false; break; } } if ((metadata.install[i].filter_regexp == null) != (oldMetadata.install[i].filter_regexp == null)) { same = false; break; } if (metadata.install[i].filter_regexp != null) { if (!metadata.install[i].filter_regexp.SequenceEqual(oldMetadata.install[i].filter_regexp)) { same = false; break; } } } } } if (!RelationshipsAreEquivalent(metadata.conflicts, oldMetadata.conflicts)) { same = false; } if (!RelationshipsAreEquivalent(metadata.depends, oldMetadata.depends)) { same = false; } if (!RelationshipsAreEquivalent(metadata.recommends, oldMetadata.recommends)) { same = false; } if (metadata.provides != oldMetadata.provides) { if (metadata.provides == null || oldMetadata.provides == null) { same = false; } else if (!metadata.provides.OrderBy(i => i).SequenceEqual(oldMetadata.provides.OrderBy(i => i))) { same = false; } } if (!same) { metadataChanges.Add(registry.available_modules[identifier].module_version[installedVersion]); } } if (metadataChanges.Any()) { var sb = new StringBuilder(); for (var i = 0; i < metadataChanges.Count; i++) { var module = metadataChanges[i]; sb.AppendLine(string.Format("- {0} {1}", module.identifier, module.version)); } if (user.RaiseYesNoDialog(string.Format(@"The following mods have had their metadata changed since last update: {0} You should reinstall them in order to preserve consistency with the repository. Do you wish to reinstall now?", sb))) { ModuleInstaller installer = ModuleInstaller.GetInstance(ksp, new NullUser()); // New upstream metadata may break the consistency of already installed modules // e.g. if user installs modules A and B and then later up A is made to conflict with B // This is perfectly normal and shouldn't produce an error, therefore we skip enforcing // consistency. However, we will show the user any inconsistencies later on. // Use the identifiers so we use the overload that actually resolves relationships // Do each changed module one at a time so a failure of one doesn't cause all the others to fail foreach (var changedIdentifier in metadataChanges.Select(i => i.identifier)) { try { installer.Upgrade( new[] { changedIdentifier }, new NetAsyncModulesDownloader(new NullUser()), enforceConsistency: false ); } // Thrown when a dependency couldn't be satisfied catch (ModuleNotFoundKraken) { log.WarnFormat("Skipping installation of {0} due to relationship error.", changedIdentifier); user.RaiseMessage("Skipping installation of {0} due to relationship error.", changedIdentifier); } // Thrown when a conflicts relationship is violated catch (InconsistentKraken) { log.WarnFormat("Skipping installation of {0} due to relationship error.", changedIdentifier); user.RaiseMessage("Skipping installation of {0} due to relationship error.", changedIdentifier); } } } } // Remove our downloaded meta-data now we've processed it. // Seems weird to do this as part of a transaction, but Net.Download uses them, so let's be consistent. file_transaction.Delete(repo_file); }
/// <summary> /// Updates the supplied registry from the URL given. /// This does not *save* the registry. For that, you probably want Repo.Update /// </summary> internal static void UpdateRegistry(Uri repo, Registry registry, KSP ksp, IUser user, Boolean clear = true) { // Use this opportunity to also update the build mappings... kind of hacky ServiceLocator.Container.Resolve<IKspBuildMap>().Refresh(); log.InfoFormat("Downloading {0}", repo); string repo_file = String.Empty; try { repo_file = Net.Download(repo); } catch (System.Net.WebException) { user.RaiseMessage("Connection to {0} could not be established.", repo); return; } // Clear our list of known modules. if (clear) { registry.ClearAvailable(); } // Check the filetype. FileType type = FileIdentifier.IdentifyFile(repo_file); switch (type) { case FileType.TarGz: UpdateRegistryFromTarGz (repo_file, registry); break; case FileType.Zip: UpdateRegistryFromZip (repo_file, registry); break; default: break; } List<CkanModule> metadataChanges = new List<CkanModule>(); foreach (var installedModule in registry.InstalledModules) { var identifier = installedModule.identifier; var installedVersion = registry.InstalledVersion(identifier); if (!(registry.available_modules.ContainsKey(identifier))) { log.InfoFormat("UpdateRegistry, module {0}, version {1} not in repository ({2})", identifier, installedVersion, repo); continue; } if (!registry.available_modules[identifier].module_version.ContainsKey(installedVersion)) { continue; } // if the mod is installed and the metadata is different we have to reinstall it var metadata = registry.available_modules[identifier].module_version[installedVersion]; var oldMetadata = registry.InstalledModule(identifier).Module; bool same = true; if ((metadata.install == null) != (oldMetadata.install == null) || (metadata.install != null && metadata.install.Length != oldMetadata.install.Length)) { same = false; } else { if(metadata.install != null) for (int i = 0; i < metadata.install.Length; i++) { if (metadata.install[i].file != oldMetadata.install[i].file) { same = false; break; } if (metadata.install[i].install_to != oldMetadata.install[i].install_to) { same = false; break; } if (metadata.install[i].@as != oldMetadata.install[i].@as) { same = false; break; } if ((metadata.install[i].filter == null) != (oldMetadata.install[i].filter == null)) { same = false; break; } if(metadata.install[i].filter != null) if (!metadata.install[i].filter.SequenceEqual(oldMetadata.install[i].filter)) { same = false; break; } if ((metadata.install[i].filter_regexp == null) != (oldMetadata.install[i].filter_regexp == null)) { same = false; break; } if(metadata.install[i].filter_regexp != null) if (!metadata.install[i].filter_regexp.SequenceEqual(oldMetadata.install[i].filter_regexp)) { same = false; break; } } } if (!RelationshipsAreEquivalent(metadata.conflicts, oldMetadata.conflicts)) same = false; if (!RelationshipsAreEquivalent(metadata.depends, oldMetadata.depends)) same = false; if (!RelationshipsAreEquivalent(metadata.recommends, oldMetadata.recommends)) same = false; if (metadata.provides != oldMetadata.provides) { if (metadata.provides == null || oldMetadata.provides == null) same = false; else if (!metadata.provides.OrderBy(i => i).SequenceEqual(oldMetadata.provides.OrderBy(i => i))) same = false; } if (!same) { metadataChanges.Add(registry.available_modules[identifier].module_version[installedVersion]); } } if (metadataChanges.Any()) { var sb = new StringBuilder(); for (var i = 0; i < metadataChanges.Count; i++) { var module = metadataChanges[i]; sb.AppendLine(string.Format("- {0} {1}", module.identifier, module.version)); } if(user.RaiseYesNoDialog(string.Format(@"The following mods have had their metadata changed since last update: {0} You should reinstall them in order to preserve consistency with the repository. Do you wish to reinstall now?", sb))) { ModuleInstaller installer = ModuleInstaller.GetInstance(ksp, new NullUser()); // New upstream metadata may break the consistency of already installed modules // e.g. if user installs modules A and B and then later up A is made to conflict with B // This is perfectly normal and shouldn't produce an error, therefore we skip enforcing // consistency. However, we will show the user any inconsistencies later on. // Use the identifiers so we use the overload that actually resolves relationships // Do each changed module one at a time so a failure of one doesn't cause all the others to fail foreach (var changedIdentifier in metadataChanges.Select(i => i.identifier)) { try { installer.Upgrade( new[] { changedIdentifier }, new NetAsyncModulesDownloader(new NullUser()), enforceConsistency: false ); } // Thrown when a dependency couldn't be satisfied catch(ModuleNotFoundKraken) { log.WarnFormat("Skipping installation of {0} due to relationship error.", changedIdentifier); user.RaiseMessage("Skipping installation of {0} due to relationship error.", changedIdentifier); } // Thrown when a conflicts relationship is violated catch (InconsistentKraken) { log.WarnFormat("Skipping installation of {0} due to relationship error.", changedIdentifier); user.RaiseMessage("Skipping installation of {0} due to relationship error.", changedIdentifier); } } } } // Remove our downloaded meta-data now we've processed it. // Seems weird to do this as part of a transaction, but Net.Download uses them, so let's be consistent. file_transaction.Delete(repo_file); }