/// <summary> /// Register the supplied module as having been installed, thereby keeping /// track of its metadata and files. /// </summary> public void RegisterModule(Module mod, IEnumerable <string> absolute_files, KSP ksp) { SealionTransaction(); // But we also want to keep track of all its files. // We start by checking to see if any files are owned by another mod, // if so, we abort with a list of errors. var inconsistencies = new List <string>(); // We always work with relative files, so let's get some! IEnumerable <string> relative_files = absolute_files.Select(x => ksp.ToRelativeGameDir(x)); foreach (string file in relative_files) { // For now, it's always cool if a module wants to register a directory. // We have to flip back to absolute paths to actually test this. if (Directory.Exists(ksp.ToAbsoluteGameDir(file))) { continue; } if (this.installed_files.ContainsKey(file)) { // Woah! Registering an already owned file? Not cool! // (Although if it existed, we should have thrown a kraken well before this.) string owner = this.installed_files[file]; inconsistencies.Add( string.Format("{0} wishes to install {1}, but this file is registered to {2}", mod.identifier, file, owner )); } } if (inconsistencies.Count > 0) { throw new InconsistentKraken(inconsistencies); } // If everything is fine, then we copy our files across. By not doing this // in the loop above, we make sure we don't have a half-registered module // when we throw our exceptinon. // This *will* result in us overwriting who owns a directory, and that's cool, // directories aren't really owned like files are. However because each mod maintains // its own list of files, we'll remove directories when the last mod using them // is uninstalled. foreach (string file in relative_files) { this.installed_files[file] = mod.identifier; } // Finally, register our module proper. var installed = new InstalledModule(ksp, mod, relative_files); installed_modules.Add(mod.identifier, installed); }
/// <summary> /// Initiate the GUI installer flow for one specific module /// </summary> /// <param name="registry">Reference to the registry</param> /// <param name="module">Module to install</param> public async void InstallModuleDriver(IRegistryQuerier registry, CkanModule module) { RelationshipResolverOptions install_ops = RelationshipResolver.DefaultOpts(); install_ops.with_recommends = false; try { var initialChangeSet = new HashSet <ModChange>(); // Install the selected mod initialChangeSet.Add(new ModChange( new GUIMod(module, registry, CurrentInstance.VersionCriteria()), GUIModChangeType.Install, null )); InstalledModule installed = registry.InstalledModule(module.identifier); if (installed != null) { // Already installed, remove it first initialChangeSet.Add(new ModChange( new GUIMod(installed.Module, registry, CurrentInstance.VersionCriteria()), GUIModChangeType.Remove, null )); } List <ModChange> fullChangeSet = new List <ModChange>( await mainModList.ComputeChangeSetFromModList( registry, initialChangeSet, ModuleInstaller.GetInstance(CurrentInstance, Manager.Cache, GUI.user), CurrentInstance.VersionCriteria() ) ); if (fullChangeSet != null && fullChangeSet.Count > 0) { // Resolve the provides relationships in the dependencies installWorker.RunWorkerAsync( new KeyValuePair <List <ModChange>, RelationshipResolverOptions>( fullChangeSet, install_ops ) ); } } catch { // If we failed, do the clean-up normally done by PostInstallMods. HideWaitDialog(false); menuStrip1.Enabled = true; } finally { changeSet = null; } }
/// <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, IDownloader 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> /// Initialize a GUIMod based on an InstalledModule /// </summary> /// <param name="instMod">The installed module to represent</param> /// <param name="registry">CKAN registry object for current game instance</param> /// <param name="current_ksp_version">Current game version</param> /// <param name="incompatible">If true, mark this module as incompatible</param> public GUIMod(InstalledModule instMod, IRegistryQuerier registry, KspVersionCriteria current_ksp_version, bool incompatible = false) : this(instMod.Module, registry, current_ksp_version, incompatible) { IsInstalled = true; IsInstallChecked = true; InstallDate = instMod.InstallTime; InstalledVersion = instMod.Module.version.ToString(); if (LatestVersion == null || LatestVersion.Equals("-")) { LatestVersion = InstalledVersion; } }
/// <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); // If there us nothing to uninstall, skip out. if (!goners.Any()) { return; } User.RaiseMessage("About to remove:\n"); foreach (string mod in goners) { InstalledModule module = registry_manager.registry.InstalledModule(mod); User.RaiseMessage(" * {0} {1}", module.Module.identifier, module.Module.modVersion); } bool ok = User.RaiseYesNoDialog("\nContinue?"); if (!ok) { User.RaiseMessage("Mod removal aborted at user request."); return; } using (var transaction = CkanTransaction.CreateTransactionScope()) { foreach (string mod in goners) { User.RaiseMessage("Removing {0}...", mod); Uninstall(mod); } registry_manager.Save(); ksp.RebuildFactorioModlist(); transaction.Complete(); } User.RaiseMessage("Done!\n"); }
/// <summary> /// Initialize a GUIMod based on an InstalledModule /// </summary> /// <param name="instMod">The installed module to represent</param> /// <param name="registry">CKAN registry object for current game instance</param> /// <param name="current_game_version">Current game version</param> /// <param name="incompatible">If true, mark this module as incompatible</param> public GUIMod(InstalledModule instMod, IRegistryQuerier registry, GameVersionCriteria current_game_version, bool?incompatible = null) : this(instMod.Module, registry, current_game_version, incompatible) { IsInstalled = true; IsInstallChecked = true; InstalledMod = instMod; selectedMod = instMod.Module; IsAutoInstalled = instMod.AutoInstalled; InstallDate = instMod.InstallTime; InstalledVersion = instMod.Module.version.ToString(); if (LatestVersion == null || LatestVersion.Equals("-")) { LatestVersion = InstalledVersion; } }
/// <summary> /// Initialize a GUIMod based on an InstalledModule /// </summary> /// <param name="instMod">The installed module to represent</param> /// <param name="registry">CKAN registry object for current game instance</param> /// <param name="current_game_version">Current game version</param> /// <param name="incompatible">If true, mark this module as incompatible</param> public GUIMod(InstalledModule instMod, IRegistryQuerier registry, GameVersionCriteria current_game_version, bool?incompatible = null) : this(instMod.Module, registry, current_game_version, incompatible) { IsInstalled = true; IsInstallChecked = true; InstalledMod = instMod; selectedMod = instMod.Module; IsAutoInstalled = instMod.AutoInstalled; InstallDate = instMod.InstallTime; InstalledVersion = instMod.Module.version.ToString(); if (LatestVersion == null || LatestVersion.Equals("-")) { LatestVersion = InstalledVersion; } // For mods not known to the registry LatestCompatibleMod is null, however the installed module might be compatible IsIncompatible = incompatible ?? LatestCompatibleMod == null && !instMod.Module.IsCompatibleKSP(current_game_version); }
/// <summary> /// Initiate the GUI installer flow for one specific module /// </summary> /// <param name="registry">Reference to the registry</param> /// <param name="module">Module to install</param> public async void InstallModuleDriver(IRegistryQuerier registry, CkanModule module) { try { var userChangeSet = new List <ModChange>(); InstalledModule installed = registry.InstalledModule(module.identifier); if (installed != null) { // Already installed, remove it first userChangeSet.Add(new ModChange( new GUIMod(installed.Module, registry, CurrentInstance.VersionCriteria()), GUIModChangeType.Remove, null )); } // Install the selected mod userChangeSet.Add(new ModChange( new GUIMod(module, registry, CurrentInstance.VersionCriteria()), GUIModChangeType.Install, null )); if (userChangeSet.Count > 0) { // Resolve the provides relationships in the dependencies installWorker.RunWorkerAsync( new KeyValuePair <List <ModChange>, RelationshipResolverOptions>( userChangeSet, RelationshipResolver.DependsOnlyOpts() ) ); } } catch { // If we failed, do the clean-up normally done by PostInstallMods. HideWaitDialog(false); menuStrip1.Enabled = true; } finally { changeSet = null; } }
/// <summary> /// Upgrades 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, NetAsyncDownloader netAsyncDownloader) { // Start by making sure we've downloaded everything. DownloadModules(modules, netAsyncDownloader); 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. \n Please remove manually before trying to install it.", ident)); } throw new ModuleNotFoundKraken( ident, module.version.ToString(), String.Format("Can't upgrade {0}, it is not installed", ident) ); } Module installed = installed_mod.Module; if (installed.version.IsEqualTo(module.version)) { log.WarnFormat("{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, modules.Select(x => x.identifier) ); }
static int Show(ShowOptions options) { RegistryManager registry_manager = RegistryManager.Instance(); InstalledModule module = registry_manager.registry.installed_modules [options.Modname]; if (module != null) { // TODO: Print *lots* of information out; I should never have to dig through JSON Console.WriteLine("{0} version {1}", module.source_module.name, module.source_module.version); Console.WriteLine("\n== Files ==\n"); Dictionary <string, InstalledModuleFile> files = module.installed_files; foreach (string file in files.Keys) { Console.WriteLine(file); } } return(EXIT_OK); }
private void DeSerialisationFixes(StreamingContext context) { // Our context is our KSP install. KSP ksp = (KSP)context.Context; // Older registries didn't have the installed_files list, so we create one // if absent. if (installed_files == null) { log.Warn("Older registry format detected, adding installed files manifest..."); ReindexInstalled(); } // If we have no registry version at all, then we're from the pre-release period. // We would check for a null here, but ints *can't* be null. if (registry_version == 0) { log.Warn("Older registry format detected, normalising paths..."); var normalised_installed_files = new Dictionary <string, string>(); foreach (KeyValuePair <string, string> tuple in installed_files) { string path = KSPPathUtils.NormalizePath(tuple.Key); if (Path.IsPathRooted(path)) { path = ksp.ToRelativeGameDir(path); normalised_installed_files[path] = tuple.Value; } else { // Already relative. normalised_installed_files[path] = tuple.Value; } } installed_files = normalised_installed_files; // Now update all our module file manifests. foreach (InstalledModule module in installed_modules.Values) { module.Renormalise(ksp); } // Our installed dlls have contained relative paths since forever, // and the next `ckan scan` will fix them anyway. (We can't scan here, // because that needs a registry, and we chicken-egg.) log.Warn("Registry upgrade complete"); } // Fix control lock, which previously was indexed with an invalid identifier. if (registry_version < 2) { InstalledModule control_lock_entry; const string old_ident = "001ControlLock"; const string new_ident = "ControlLock"; if (installed_modules.TryGetValue("001ControlLock", out control_lock_entry)) { if (ksp == null) { throw new Kraken("Internal bug: No KSP instance provided on registry deserialisation"); } log.WarnFormat("Older registry detected. Reindexing {0} as {1}. This may take a moment.", old_ident, new_ident); // Remove old record. installed_modules.Remove(old_ident); // Extract the old module metadata CkanModule control_lock_mod = control_lock_entry.Module; // Change to the correct ident. control_lock_mod.identifier = new_ident; // Prepare to re-index. var new_control_lock_installed = new InstalledModule( ksp, control_lock_mod, control_lock_entry.Files ); // Re-insert into registry. installed_modules[new_control_lock_installed.identifier] = new_control_lock_installed; // Re-index files. ReindexInstalled(); } } // If we spot a default repo with the old .zip URL, flip it to the new .tar.gz URL // Any other repo we leave *as-is*, even if it's the github meta-repo, as it's been // custom-added by our user. Repository default_repo; var oldDefaultRepo = new Uri("https://github.com/KSP-CKAN/CKAN-meta/archive/master.zip"); if (repositories != null && repositories.TryGetValue(Repository.default_ckan_repo_name, out default_repo) && default_repo.uri == oldDefaultRepo) { log.InfoFormat("Updating default metadata URL from {0} to {1}", oldDefaultRepo, Repository.default_ckan_repo_uri); repositories["default"].uri = Repository.default_ckan_repo_uri; } registry_version = LATEST_REGISTRY_VERSION; }
/// <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 TransactionScope()) { 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.WriteLine("Not removing directory {0}, it's not empty", directory); } } transaction.Complete(); } return; }
private void DeSerialisationFixes(StreamingContext context) { // Our context is our KSP install. KSP ksp = (KSP) context.Context; // Older registries didn't have the installed_files list, so we create one // if absent. if (installed_files == null) { log.Warn("Older registry format detected, adding installed files manifest..."); ReindexInstalled(); } // If we have no registry version at all, then we're from the pre-release period. // We would check for a null here, but ints *can't* be null. if (registry_version == 0) { log.Warn("Older registry format detected, normalising paths..."); var normalised_installed_files = new Dictionary<string,string>(); foreach (KeyValuePair<string,string> tuple in installed_files) { string path = KSPPathUtils.NormalizePath(tuple.Key); if (Path.IsPathRooted(path)) { path = ksp.ToRelativeGameDir(path); normalised_installed_files[path] = tuple.Value; } else { // Already relative. normalised_installed_files[path] = tuple.Value; } } installed_files = normalised_installed_files; // Now update all our module file manifests. foreach (InstalledModule module in installed_modules.Values) { module.Renormalise(ksp); } // Our installed dlls have contained relative paths since forever, // and the next `ckan scan` will fix them anyway. (We can't scan here, // because that needs a registry, and we chicken-egg.) log.Warn("Registry upgrade complete"); } // Fix control lock, which previously was indexed with an invalid identifier. if (registry_version < 2) { InstalledModule control_lock_entry; const string old_ident = "001ControlLock"; const string new_ident = "ControlLock"; if (installed_modules.TryGetValue("001ControlLock", out control_lock_entry)) { if (ksp == null) { throw new Kraken("Internal bug: No KSP instance provided on registry deserialisation"); } log.WarnFormat("Older registry detected. Reindexing {0} as {1}. This may take a moment.", old_ident, new_ident); // Remove old record. installed_modules.Remove(old_ident); // Extract the old module metadata Module control_lock_mod = control_lock_entry.Module; // Change to the correct ident. control_lock_mod.identifier = new_ident; // Prepare to re-index. var new_control_lock_installed = new InstalledModule( ksp, control_lock_mod, control_lock_entry.Files ); // Re-insert into registry. installed_modules[new_control_lock_installed.identifier] = new_control_lock_installed; // Re-index files. ReindexInstalled(); } } registry_version = LATEST_REGISTRY_VERSION; }
/// <summary> /// Register the supplied module as having been installed, thereby keeping /// track of its metadata and files. /// </summary> public void RegisterModule(Module mod, IEnumerable<string> absolute_files, KSP ksp) { SealionTransaction(); // But we also want to keep track of all its files. // We start by checking to see if any files are owned by another mod, // if so, we abort with a list of errors. var inconsistencies = new List<string>(); // We always work with relative files, so let's get some! IEnumerable<string> relative_files = absolute_files.Select(x => ksp.ToRelativeGameDir(x)); // For now, it's always cool if a module wants to register a directory. // We have to flip back to absolute paths to actually test this. foreach (string file in relative_files.Where(file => !Directory.Exists(ksp.ToAbsoluteGameDir(file)))) { string owner; if (installed_files.TryGetValue(file, out owner)) { // Woah! Registering an already owned file? Not cool! // (Although if it existed, we should have thrown a kraken well before this.) inconsistencies.Add( string.Format("{0} wishes to install {1}, but this file is registered to {2}", mod.identifier, file, owner )); } } if (inconsistencies.Count > 0) { throw new InconsistentKraken(inconsistencies); } // If everything is fine, then we copy our files across. By not doing this // in the loop above, we make sure we don't have a half-registered module // when we throw our exceptinon. // This *will* result in us overwriting who owns a directory, and that's cool, // directories aren't really owned like files are. However because each mod maintains // its own list of files, we'll remove directories when the last mod using them // is uninstalled. foreach (string file in relative_files) { installed_files[file] = mod.identifier; } // Finally, register our module proper. var installed = new InstalledModule(ksp, mod, relative_files); installed_modules.Add(mod.identifier, installed); }
/// <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 = CkanTransaction.CreateTransactionScope()) { 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); // [This is] bitwise math. Basically, attr is some binary value with one bit meaning // "this is a directory". The bitwise and & operator will return a binary value where // only the bits that are on (1) in both the operands are turned on. In this case // doing a bitwise and operation against attr and the FileAttributes.Directory value // will return the value of FileAttributes.Directory if the Directory file attribute // bit is turned on. See en.wikipedia.org/wiki/Bitwise_operation for a better // explanation. – Kyle Trauberman Aug 30 '12 at 21:28 // (https://stackoverflow.com/questions/1395205/better-way-to-check-if-path-is-a-file-or-a-directory) // This is the fastest way to do this test. if ((attr & FileAttributes.Directory) == FileAttributes.Directory) { if (!directoriesToDelete.Contains(path)) { directoriesToDelete.Add(path); } } else { // Add this files' directory to the list for deletion if it isn't already there. // Helps clean up directories when modules are uninstalled out of dependency order // Since we check for directory contents when deleting, this should purge empty // dirs, making less ModuleManager headaches for people. var directoryName = Path.GetDirectoryName(path); if (!(directoriesToDelete.Contains(directoryName))) { directoriesToDelete.Add(directoryName); } log.DebugFormat("Removing {0}", file); file_transaction.Delete(path); } } catch (Exception ex) { // XXX: This is terrible, we're catching all exceptions. // We don't consider this problem serious enough to abort and revert, // so treat it as a "--verbose" level log message. log.InfoFormat("Failure in locating file {0} : {1}", path, ex.Message); } } // Remove from registry. registry_manager.registry.DeregisterModule(ksp, modName); // Our collection of directories may leave empty parent directories. directoriesToDelete = AddParentDirectories(directoriesToDelete); // 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()) { // It is bad if any of this directories gets removed // So we protect them if (IsReservedDirectory(directory)) { continue; } // 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.DebugFormat("Removing {0}", directory); Directory.Delete(directory); } else { log.InfoFormat("Not removing directory {0}, it's not empty", directory); } } log.InfoFormat("Removed {0}", modName); transaction.Complete(); } }
/// <summary> /// Register the supplied module as having been installed, thereby keeping /// track of its metadata and files. /// </summary> /// <param name="mod">Mod.</param> public void RegisterModule (InstalledModule mod) { installed_modules.Add (mod.source_module.identifier, mod); }