/// <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 (TransactionScope tx = CkanTransaction.CreateTransactionScope()) { 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> /// Copies a directory and optionally its subdirectories as a transaction. /// </summary> /// <param name="sourceDirPath">Source directory path.</param> /// <param name="destDirPath">Destination directory path.</param> /// <param name="copySubDirs">Copy sub dirs recursively if set to <c>true</c>.</param> public static void CopyDirectory(string sourceDirPath, string destDirPath, bool copySubDirs) { using (TransactionScope transaction = CkanTransaction.CreateTransactionScope()) { _CopyDirectory(sourceDirPath, destDirPath, copySubDirs); transaction.Complete(); } }
/// <summary> /// Copies a directory and optionally its subdirectories as a transaction. /// </summary> /// <param name="sourceDirPath">Source directory path.</param> /// <param name="destDirPath">Destination directory path.</param> /// <param name="copySubDirs">Copy sub dirs recursively if set to <c>true</c>.</param> public static void CopyDirectory(string sourceDirPath, string destDirPath, bool copySubDirs) { TxFileManager file_transaction = new TxFileManager(); using (TransactionScope transaction = CkanTransaction.CreateTransactionScope()) { _CopyDirectory(sourceDirPath, destDirPath, copySubDirs, file_transaction); transaction.Complete(); } }
/// <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"); }
protected override void OnFormClosing(FormClosingEventArgs e) { // Only close the window, when the user has access to the "Exit" of the menu. if (!menuStrip1.Enabled) { e.Cancel = true; return; } if (!ManageMods.AllowClose()) { e.Cancel = true; return; } // Copy window location to app settings configuration.WindowLoc = WindowState == FormWindowState.Normal ? Location : RestoreBounds.Location; // Copy window size to app settings if not maximized configuration.WindowSize = WindowState == FormWindowState.Normal ? Size : RestoreBounds.Size; //copy window maximized state to app settings configuration.IsWindowMaximised = WindowState == FormWindowState.Maximized; // Copy panel position to app settings configuration.PanelPosition = splitContainer1.SplitterDistance; // Copy metadata panel split height to app settings configuration.ModInfoPosition = ModInfo.ModMetaSplitPosition; // Save the active filter configuration.ActiveFilter = (int)ManageMods.mainModList.ModFilter; configuration.CustomLabelFilter = ManageMods.mainModList.CustomLabelFilter?.Name; // Save settings. configuration.Save(); if (needRegistrySave) { using (var transaction = CkanTransaction.CreateTransactionScope()) { // Save registry RegistryManager.Instance(CurrentInstance).Save(false); transaction.Complete(); } } base.OnFormClosing(e); }
/// <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) { CheckMetapackageInstallationKraken(module); 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, true); // 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 or download is corrupted", module) ); } // We'll need our registry to record which files we've installed. Registry registry = registry_manager.registry; using (var transaction = CkanTransaction.CreateTransactionScope()) { // 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> /// Clears the registry of DLL data, and refreshes it by scanning GameData. /// This operates as a transaction. /// This *saves* the registry upon completion. /// TODO: This would likely be better in the Registry class itself. /// </summary> /// <returns> /// True if found anything different, false if same as before /// </returns> public bool Scan() { if (Directory.Exists(game.PrimaryModDirectory(this))) { var manager = RegistryManager.Instance(this); using (TransactionScope tx = CkanTransaction.CreateTransactionScope()) { log.DebugFormat("Scanning for DLLs in {0}", game.PrimaryModDirectory(this)); var oldDlls = manager.registry.InstalledDlls.ToHashSet(); manager.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(game.PrimaryModDirectory(this), "*", SearchOption.AllDirectories) .Where(file => file.EndsWith(".dll", StringComparison.CurrentCultureIgnoreCase)) .Select(CKANPathUtils.NormalizePath) .Where(absPath => !game.StockFolders.Any(f => ToRelativeGameDir(absPath).StartsWith($"{f}/"))); foreach (string dll in files) { manager.registry.RegisterDll(this, dll); } var newDlls = manager.registry.InstalledDlls.ToHashSet(); bool dllChanged = !oldDlls.SetEquals(newDlls); bool dlcChanged = manager.ScanDlc(); if (dllChanged || dlcChanged) { manager.Save(false); } log.Debug("Scan completed, committing transaction"); tx.Complete(); return(dllChanged || dlcChanged); } } return(false); }
/// <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> /// Clears the registry of DLL data, and refreshes it by scanning GameData. /// This operates as a transaction. /// This *saves* the registry upon completion. /// TODO: This would likely be better in the Registry class itself. /// </summary> /// <returns> /// True if found anything different, false if same as before /// </returns> public bool ScanGameData() { var manager = RegistryManager.Instance(this); using (TransactionScope tx = CkanTransaction.CreateTransactionScope()) { var oldDlls = new HashSet <string>(manager.registry.InstalledDlls); manager.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) .Where(file => dllRegex.IsMatch(file)) .Select(KSPPathUtils.NormalizePath) .Where(absPath => !DllIgnoreList.Contains(ToRelativeGameDir(absPath))); foreach (string dll in files) { manager.registry.RegisterDll(this, dll); } var newDlls = new HashSet <string>(manager.registry.InstalledDlls); bool dllChanged = !oldDlls.SetEquals(newDlls); bool dlcChanged = manager.ScanDlc(); if (dllChanged || dlcChanged) { manager.Save(false); } tx.Complete(); return(dllChanged || dlcChanged); } }
/// <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, bool enforceConsistency = true) { // TODO: We should do a consistency check up-front, rather than relying // upon our registry catching inconsistencies at the end. using (var tx = CkanTransaction.CreateTransactionScope()) { foreach (string identifier in remove) { Uninstall(identifier); } foreach (CkanModule module in add) { Install(module); } registry_manager.Save(enforceConsistency); tx.Complete(); } }
public void InstallList(ModuleResolution modules, RelationshipResolverOptions options) { // We're about to install all our mods; so begin our transaction. using (TransactionScope transaction = CkanTransaction.CreateTransactionScope()) { var enumeratedMods = modules.Select((m, i) => new { Idx = i, Module = m }); foreach (var item in enumeratedMods) { var percentComplete = (item.Idx * 100) / modules.Count; User.RaiseProgress(string.Format("Installing mod \"{0}\"", item.Module), percentComplete); Install(item.Module); } User.RaiseProgress("Updating registry", 70); registry_manager.Save(!options.without_enforce_consistency); User.RaiseProgress("Commiting filesystem changes", 80); transaction.Complete(); } }
/// <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(); } }
/// <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); 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()) { // Skip Ships/VAB ans Ships/SPH if (directory == KSPPathUtils.ToAbsolute("VAB", ksp.Ships()) || directory == KSPPathUtils.ToAbsolute("SPH", ksp.Ships())) { 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.InfoFormat("Removing {0}", directory); Directory.Delete(directory); } else { log.InfoFormat("Not removing directory {0}, it's not empty", directory); } } transaction.Complete(); } }
/// <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 (TransactionScope tx = CkanTransaction.CreateTransactionScope()) { Registry.ClearPreexistingModules(); var detectedModules = FactorioModDetector.findAllModsInDirectory(Path.Combine(gamedatadir, "mods")); foreach (var detectedModule in detectedModules) { string detectedModulePath = detectedModule.Key; ModInfoJson detectedModInfo = detectedModule.Value; if (Registry.InstalledModules.Any(p => p.identifier == detectedModInfo.name)) { continue; } AvailableModule availableModule; if (Registry.available_modules.TryGetValue(detectedModInfo.name, out availableModule)) { CfanModule availableCfan = availableModule.ByVersion(detectedModInfo.version); if (availableCfan != null) { string expectedFilename = availableCfan.standardFileName + ".zip"; if (Path.GetFileName(detectedModulePath) == expectedFilename) { // yay, we can use this mod as installed (we will be able to update/remove it through cfan) Registry.RegisterModule(availableCfan, new [] { detectedModulePath }, this); continue; } } } // we only register that this module exists, but we won't be able to do anything with it Registry.RegisterPreexistingModule(this, detectedModulePath, detectedModInfo); } try { Registry.CheckSanity(); } catch (InconsistentKraken e) { User.RaiseError("Autodetected mods has unmet dependencies, they won't be managed by CFAN until you fix inconsitencies.\n{0}", e.InconsistenciesPretty); var unmet = SanityChecker.FindUnmetDependencies(Registry.InstalledModules.Select(p => p.Module), Registry.InstalledPreexistingModules); do { unmet .Select(unmetDependencyKeyValue => unmetDependencyKeyValue.Value) .SelectMany(p => p.Select(modWithUnmetDependencies => detectedModules.FirstOrDefault(detectedModule => detectedModule.Value.name == modWithUnmetDependencies.identifier))) .Where(p => !p.Equals(default(KeyValuePair <string, ModInfoJson>))) .ToList() .ForEach(modWithUnmetDependencies => { Registry.DeregisterModule(this, modWithUnmetDependencies.Value.name, true); Registry.RegisterPreexistingModule(this, modWithUnmetDependencies.Key, modWithUnmetDependencies.Value); }); unmet = SanityChecker.FindUnmetDependencies(Registry.InstalledModules.Select(p => p.Module), Registry.InstalledPreexistingModules); } while (unmet.Any()); } tx.Complete(); } RegistryManager.Save(); }
/// <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.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 (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!\n", 100); }
private void InstallMods(object sender, DoWorkEventArgs e) // this probably needs to be refactored { 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; }; 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 f***s 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 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(resolvedMods, opts.Value); 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); }
/// <summary> /// Create a new fake KSP instance /// </summary> /// <param name="game">The game of the new instance.</param> /// <param name="newName">The name for the new instance.</param> /// <param name="newPath">The location of the new instance.</param> /// <param name="version">The version of the new instance. Should have a build number.</param> /// <param name="dlcs">The IDlcDetector implementations for the DLCs that should be faked and the requested dlc version as a dictionary.</param> /// <exception cref="InstanceNameTakenKraken">Thrown if the instance name is already in use.</exception> /// <exception cref="NotKSPDirKraken">Thrown by AddInstance() if created instance is not valid, e.g. if a write operation didn't complete for whatever reason.</exception> public void FakeInstance(IGame game, string newName, string newPath, GameVersion version, Dictionary <DLC.IDlcDetector, GameVersion> dlcs = null) { TxFileManager fileMgr = new TxFileManager(); using (TransactionScope transaction = CkanTransaction.CreateTransactionScope()) { if (HasInstance(newName)) { throw new InstanceNameTakenKraken(newName); } if (!version.InBuildMap(game)) { throw new BadGameVersionKraken(String.Format("The specified KSP version is not a known version: {0}", version.ToString())); } if (Directory.Exists(newPath) && (Directory.GetFiles(newPath).Length != 0 || Directory.GetDirectories(newPath).Length != 0)) { throw new BadInstallLocationKraken("The specified folder already exists and is not empty."); } log.DebugFormat("Creating folder structure and text files at {0} for KSP version {1}", Path.GetFullPath(newPath), version.ToString()); // Create a KSP root directory, containing a GameData folder, a buildID.txt/buildID64.txt and a readme.txt fileMgr.CreateDirectory(newPath); fileMgr.CreateDirectory(Path.Combine(newPath, "GameData")); fileMgr.CreateDirectory(Path.Combine(newPath, "Ships")); fileMgr.CreateDirectory(Path.Combine(newPath, "Ships", "VAB")); fileMgr.CreateDirectory(Path.Combine(newPath, "Ships", "SPH")); fileMgr.CreateDirectory(Path.Combine(newPath, "Ships", "@thumbs")); fileMgr.CreateDirectory(Path.Combine(newPath, "Ships", "@thumbs", "VAB")); fileMgr.CreateDirectory(Path.Combine(newPath, "Ships", "@thumbs", "SPH")); fileMgr.CreateDirectory(Path.Combine(newPath, "saves")); fileMgr.CreateDirectory(Path.Combine(newPath, "saves", "scenarios")); fileMgr.CreateDirectory(Path.Combine(newPath, "saves", "training")); // Don't write the buildID.txts if we have no build, otherwise it would be -1. if (version.IsBuildDefined) { fileMgr.WriteAllText(Path.Combine(newPath, "buildID.txt"), String.Format("build id = {0}", version.Build)); fileMgr.WriteAllText(Path.Combine(newPath, "buildID64.txt"), String.Format("build id = {0}", version.Build)); } // Create the readme.txt WITHOUT build number. fileMgr.WriteAllText(Path.Combine(newPath, "readme.txt"), String.Format("Version {0}", new GameVersion(version.Major, version.Minor, version.Patch).ToString())); // Create the needed folder structure and the readme.txt for DLCs that should be simulated. if (dlcs != null) { foreach (KeyValuePair <DLC.IDlcDetector, GameVersion> dlc in dlcs) { DLC.IDlcDetector dlcDetector = dlc.Key; GameVersion dlcVersion = dlc.Value; if (!dlcDetector.AllowedOnBaseVersion(version)) { throw new WrongGameVersionKraken( version, String.Format("KSP version {0} or above is needed for {1} DLC.", dlcDetector.ReleaseGameVersion, dlcDetector.IdentifierBaseName )); } string dlcDir = Path.Combine(newPath, dlcDetector.InstallPath()); fileMgr.CreateDirectory(dlcDir); fileMgr.WriteAllText( Path.Combine(dlcDir, "readme.txt"), String.Format("Version {0}", dlcVersion)); } } // Add the new instance to the config GameInstance new_instance = new GameInstance(game, newPath, newName, User, false); AddInstance(new_instance); transaction.Complete(); } }
/// <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> /// Download and update the local CKAN meta-info. /// Optionally takes a URL to the zipfile repo to download. /// </summary> public static RepoUpdateResult UpdateAllRepositories(RegistryManager registry_manager, GameInstance ksp, NetModuleCache cache, IUser user) { SortedDictionary <string, Repository> sortedRepositories = registry_manager.registry.Repositories; user.RaiseProgress("Checking for updates", 0); if (sortedRepositories.Values.All(repo => !string.IsNullOrEmpty(repo.last_server_etag) && repo.last_server_etag == Net.CurrentETag(repo.uri))) { user.RaiseProgress("Already up to date", 100); user.RaiseMessage("No changes since last update"); return(RepoUpdateResult.NoChanges); } List <CkanModule> allAvail = new List <CkanModule>(); int index = 0; foreach (KeyValuePair <string, Repository> repository in sortedRepositories) { user.RaiseProgress($"Updating {repository.Value.name}", 10 + 80 * index / sortedRepositories.Count); SortedDictionary <string, int> downloadCounts; string newETag; List <CkanModule> avail = UpdateRegistry(repository.Value.uri, ksp, user, out downloadCounts, out newETag); registry_manager.registry.SetDownloadCounts(downloadCounts); if (avail == null) { // Report failure if any repo fails, rather than losing half the list. // UpdateRegistry will have alerted the user to specific errors already. return(RepoUpdateResult.Failed); } else { // Merge all the lists allAvail.AddRange(avail); repository.Value.last_server_etag = newETag; user.RaiseMessage("Updated {0} ({1} modules)", repository.Value.name, avail.Count); } ++index; } // Save allAvail to the registry if we found anything if (allAvail.Count > 0) { user.RaiseProgress("Saving modules to registry", 90); using (var transaction = CkanTransaction.CreateTransactionScope()) { // Save our changes. registry_manager.registry.SetAllAvailable(allAvail); registry_manager.Save(enforce_consistency: false); transaction.Complete(); } ShowUserInconsistencies(registry_manager.registry, user); List <CkanModule> metadataChanges = GetChangedInstalledModules(registry_manager.registry); if (metadataChanges.Count > 0) { HandleModuleChanges(metadataChanges, user, ksp, cache, registry_manager); } // Registry.CompatibleModules is slow, just return success, // caller can check it if it's really needed user.RaiseProgress("Registry saved", 100); user.RaiseMessage("Repositories updated"); return(RepoUpdateResult.Updated); } else { // Return failure user.RaiseMessage("No modules found!"); return(RepoUpdateResult.Failed); } }
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); }