diff --git a/CKAN.schema b/CKAN.schema index 3aab11ee07..133780a4da 100644 --- a/CKAN.schema +++ b/CKAN.schema @@ -144,6 +144,25 @@ "items" : { "type" : "string" }, "uniqueItems" : true }, + "replaced_by" : { + "description" : "Optional pointer to mod that should be selected instead and treated as an update to this mod", + "type" : "object", + "properties" : { + "name" : { + "description" : "Identifier of the mod", + "$ref" : "#/definitions/identifier" + }, + "version" : { + "description" : "Optional version", + "$ref" : "#/definitions/version" + }, + "min_version" : { + "description" : "Optional minimum version", + "$ref" : "#/definitions/version" + } + }, + "required" : [ "name" ] + }, "resources" : { "description" : "Additional resources", "type" : "object", diff --git a/Cmdline/Action/Install.cs b/Cmdline/Action/Install.cs index 33f3be68eb..c9289b90c7 100644 --- a/Cmdline/Action/Install.cs +++ b/Cmdline/Action/Install.cs @@ -67,7 +67,7 @@ public int RunCommand(CKAN.KSP ksp, object raw_options) // Parse the JSON file. try { - CkanModule m = LoadCkanFromFile(ksp, filename); + CkanModule m = MainClass.LoadCkanFromFile(ksp, filename); options.modules.Add($"{m.identifier}={m.version}"); } catch (Kraken kraken) @@ -257,21 +257,5 @@ public int RunCommand(CKAN.KSP ksp, object raw_options) return Exit.OK; } - - internal static CkanModule LoadCkanFromFile(CKAN.KSP current_instance, string ckan_file) - { - CkanModule module = CkanModule.FromFile(ckan_file); - - // We'll need to make some registry changes to do this. - RegistryManager registry_manager = RegistryManager.Instance(current_instance); - - // Remove this version of the module in the registry, if it exists. - registry_manager.registry.RemoveAvailable(module); - - // Sneakily add our version in... - registry_manager.registry.AddAvailable(module); - - return module; - } } } diff --git a/Cmdline/Action/List.cs b/Cmdline/Action/List.cs index e191def617..de39e91e71 100644 --- a/Cmdline/Action/List.cs +++ b/Cmdline/Action/List.cs @@ -50,7 +50,7 @@ public int RunCommand(CKAN.KSP ksp, object raw_options) foreach (KeyValuePair mod in installed) { Version current_version = mod.Value; - + string modInfo = string.Format("{0} {1}", mod.Key, mod.Value); string bullet = "*"; if (current_version is ProvidesVersion) @@ -61,26 +61,52 @@ public int RunCommand(CKAN.KSP ksp, object raw_options) else if (current_version is DllVersion) { // Autodetected dll - bullet = "-"; + bullet = "A"; } else { try { // Check if upgrades are available, and show appropriately. + log.DebugFormat("Check if upgrades are available for {0}", mod.Key); CkanModule latest = registry.LatestAvailable(mod.Key, ksp.VersionCriteria()); - - log.InfoFormat("Latest {0} is {1}", mod.Key, latest); + CkanModule current = registry.GetInstalledVersion(mod.Key); if (latest == null) { // Not compatible! + log.InfoFormat("Latest {0} is not compatible", mod.Key); bullet = "X"; + if ( current == null ) log.DebugFormat( " {0} installed version not found in registry", mod.Key); + + //Check if mod is replaceable + if ( current.replaced_by != null ) + { + ModuleReplacement replacement = registry.GetReplacement(mod.Key, ksp.VersionCriteria()); + if ( replacement != null ) + { + //Replaceable! + bullet = ">"; + modInfo = string.Format("{0} > {1} {2}", modInfo, replacement.ReplaceWith.name, replacement.ReplaceWith.version); + } + } } else if (latest.version.IsEqualTo(current_version)) { // Up to date + log.InfoFormat("Latest {0} is {1}", mod.Key, latest.version); bullet = "-"; + //Check if mod is replaceable + if ( current.replaced_by != null ) + { + ModuleReplacement replacement = registry.GetReplacement(latest.identifier, ksp.VersionCriteria()); + if ( replacement != null ) + { + //Replaceable! + bullet = ">"; + modInfo = string.Format("{0} > {1} {2}", modInfo, replacement.ReplaceWith.name, replacement.ReplaceWith.version); + } + } } else if (latest.version.IsGreaterThan(mod.Value)) { @@ -95,7 +121,7 @@ public int RunCommand(CKAN.KSP ksp, object raw_options) } } - user.RaiseMessage("{0} {1} {2}", bullet, mod.Key, mod.Value); + user.RaiseMessage("{0} {1}", bullet, modInfo); } } else @@ -107,7 +133,7 @@ public int RunCommand(CKAN.KSP ksp, object raw_options) if (!(options.porcelain) && exportFileType == null) { - user.RaiseMessage("\r\nLegend: -: Up to date. X: Incompatible. ^: Upgradable. ?: Unknown. *: Broken. "); + user.RaiseMessage("\r\nLegend: -: Up to date. X: Incompatible. ^: Upgradable. >: Replaceable\r\n A: Autodetected. ?: Unknown. *: Broken. "); // Broken mods are in a state that CKAN doesn't understand, and therefore can't handle automatically } diff --git a/Cmdline/Action/Replace.cs b/Cmdline/Action/Replace.cs new file mode 100644 index 0000000000..4529dfd1bd --- /dev/null +++ b/Cmdline/Action/Replace.cs @@ -0,0 +1,172 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using log4net; + +namespace CKAN.CmdLine +{ + public class Replace : ICommand + { + private static readonly ILog log = LogManager.GetLogger(typeof(Replace)); + + public IUser User { get; set; } + + public Replace(IUser user) + { + User = user; + } + + + public int RunCommand(CKAN.KSP ksp, object raw_options) + { + ReplaceOptions options = (ReplaceOptions) raw_options; + + if (options.ckan_file != null) + { + options.modules.Add(MainClass.LoadCkanFromFile(ksp, options.ckan_file).identifier); + } + + if (options.modules.Count == 0 && ! options.replace_all) + { + // What? No mods specified? + User.RaiseMessage("Usage: ckan replace Mod [Mod2, ...]"); + User.RaiseMessage(" or ckan replace --all"); + return Exit.BADOPT; + } + + // Prepare options. Can these all be done in the new() somehow? + var replace_ops = new RelationshipResolverOptions + { + with_all_suggests = options.with_all_suggests, + with_suggests = options.with_suggests, + with_recommends = !options.no_recommends, + allow_incompatible = options.allow_incompatible + }; + + var registry = RegistryManager.Instance(ksp).registry; + var to_replace = new List(); + + if (options.replace_all) + { + log.Debug("Running Replace all"); + var installed = new Dictionary(registry.Installed()); + + foreach (KeyValuePair mod in installed) + { + Version current_version = mod.Value; + + if ((current_version is ProvidesVersion) || (current_version is DllVersion)) + { + continue; + } + else + { + try + { + log.DebugFormat("Testing {0} {1} for possible replacement", mod.Key, mod.Value); + // Check if replacement is available + + ModuleReplacement replacement = registry.GetReplacement(mod.Key, ksp.VersionCriteria()); + if (replacement != null) + { + // Replaceable + log.InfoFormat("Replacement {0} {1} found for {2} {3}", + replacement.ReplaceWith.identifier, replacement.ReplaceWith.version, + replacement.ToReplace.identifier, replacement.ToReplace.version); + to_replace.Add(replacement); + } + } + catch (ModuleNotFoundKraken) + { + log.InfoFormat("{0} is installed, but it or its replacement is not in the registry", + mod.Key); + } + } + } + } + else + { + foreach (string mod in options.modules) + { + try + { + log.DebugFormat("Checking that {0} is installed", mod); + CkanModule modToReplace = registry.GetInstalledVersion(mod); + if ( modToReplace != null) + { + log.DebugFormat("Testing {0} {1} for possible replacement", modToReplace.identifier, modToReplace.version); + try + { + // Check if replacement is available + ModuleReplacement replacement = registry.GetReplacement(modToReplace.identifier, ksp.VersionCriteria()); + if (replacement != null) + { + // Replaceable + log.InfoFormat("Replacement {0} {1} found for {2} {3}", + replacement.ReplaceWith.identifier, replacement.ReplaceWith.version, + replacement.ToReplace.identifier, replacement.ToReplace.version); + to_replace.Add(replacement); + } + if (modToReplace.replaced_by != null) + { + log.InfoFormat("Attempt to replace {0} failed, replacement {1} is not compatible", + mod, modToReplace.replaced_by.name); + } + else + { + log.InfoFormat("Mod {0} has no replacement defined for the current version {1}", + modToReplace.identifier, modToReplace.version); + } + } + catch (ModuleNotFoundKraken) + { + log.InfoFormat("{0} is installed, but its replacement {1} is not in the registry", + mod, modToReplace.replaced_by.name); + } + } + } + catch (ModuleNotFoundKraken kraken) + { + User.RaiseMessage("Module {0} not found", kraken.module); + } + } + } + if (to_replace.Count() != 0) + { + User.RaiseMessage("\r\nReplacing modules...\r\n"); + foreach (ModuleReplacement r in to_replace) + { + User.RaiseMessage("Replacement {0} {1} found for {2} {3}", + r.ReplaceWith.identifier, r.ReplaceWith.version, + r.ToReplace.identifier, r.ToReplace.version); + } + + bool ok = User.RaiseYesNoDialog("\r\nContinue?"); + + if (!ok) + { + User.RaiseMessage("Replacements canceled at user request."); + return Exit.ERROR; + } + + // TODO: These instances all need to go. + try + { + ModuleInstaller.GetInstance(ksp, User).Replace(to_replace, replace_ops, new NetAsyncModulesDownloader(User)); + User.RaiseMessage("\r\nDone!\r\n"); + } + catch (DependencyNotSatisfiedKraken ex) + { + User.RaiseMessage("Dependencies not satisfied for replacement, {0} requires {1} {2} but it is not listed in the index, or not available for your version of KSP.", ex.parent, ex.module, ex.version); + } + } + else + { + User.RaiseMessage("No replacements found."); + return Exit.OK; + } + + return Exit.OK; + } + } +} \ No newline at end of file diff --git a/Cmdline/Action/Upgrade.cs b/Cmdline/Action/Upgrade.cs index 797d1bdbe8..f1b38384fa 100644 --- a/Cmdline/Action/Upgrade.cs +++ b/Cmdline/Action/Upgrade.cs @@ -20,8 +20,8 @@ public int RunCommand(CKAN.KSP ksp, object raw_options) UpgradeOptions options = (UpgradeOptions) raw_options; if (options.ckan_file != null) - { - options.modules.Add(LoadCkanFromFile(ksp, options.ckan_file).identifier); + { + options.modules.Add(MainClass.LoadCkanFromFile(ksp, options.ckan_file).identifier); } if (options.modules.Count == 0 && ! options.upgrade_all) @@ -132,21 +132,5 @@ public int RunCommand(CKAN.KSP ksp, object raw_options) return Exit.OK; } - - internal static CkanModule LoadCkanFromFile(CKAN.KSP current_instance, string ckan_file) - { - CkanModule module = CkanModule.FromFile(ckan_file); - - // We'll need to make some registry changes to do this. - RegistryManager registry_manager = RegistryManager.Instance(current_instance); - - // Remove this version of the module in the registry, if it exists. - registry_manager.registry.RemoveAvailable(module); - - // Sneakily add our version in... - registry_manager.registry.AddAvailable(module); - - return module; - } } } diff --git a/Cmdline/CKAN-cmdline.csproj b/Cmdline/CKAN-cmdline.csproj index 409b389768..db9bdac6d9 100644 --- a/Cmdline/CKAN-cmdline.csproj +++ b/Cmdline/CKAN-cmdline.csproj @@ -37,14 +37,14 @@ ..\_build\lib\nuget\CommandLineParser.1.9.71\lib\net45\CommandLine.dll - + + + ..\_build\lib\nuget\log4net.2.0.8\lib\net45-full\log4net.dll - + ..\_build\lib\nuget\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll - - @@ -63,6 +63,7 @@ + diff --git a/Cmdline/Main.cs b/Cmdline/Main.cs index 13cb65ce61..eed354da99 100644 --- a/Cmdline/Main.cs +++ b/Cmdline/Main.cs @@ -163,6 +163,14 @@ private static int RunSimpleAction(Options cmdline, CommonOptions options, strin case "show": return (new Show(user)).RunCommand(GetGameInstance(manager), (ShowOptions)cmdline.options); + case "replace": + Scan(GetGameInstance(manager), user, cmdline.action); + return (new Replace(user)).RunCommand(GetGameInstance(manager), (ReplaceOptions)cmdline.options); + + case "upgrade": + Scan(GetGameInstance(manager), user, cmdline.action); + return (new Upgrade(user)).RunCommand(GetGameInstance(manager), cmdline.options); + case "search": return (new Search(user)).RunCommand(GetGameInstance(manager), options); @@ -170,10 +178,6 @@ private static int RunSimpleAction(Options cmdline, CommonOptions options, strin case "remove": return (new Remove(user)).RunCommand(GetGameInstance(manager), cmdline.options); - case "upgrade": - Scan(GetGameInstance(manager), user, cmdline.action); - return (new Upgrade(user)).RunCommand(GetGameInstance(manager), cmdline.options); - case "clean": return Clean(GetGameInstance(manager)); @@ -195,6 +199,22 @@ private static int RunSimpleAction(Options cmdline, CommonOptions options, strin } } + internal static CkanModule LoadCkanFromFile(CKAN.KSP current_instance, string ckan_file) + { + CkanModule module = CkanModule.FromFile(ckan_file); + + // We'll need to make some registry changes to do this. + RegistryManager registry_manager = RegistryManager.Instance(current_instance); + + // Remove this version of the module in the registry, if it exists. + registry_manager.registry.RemoveAvailable(module); + + // Sneakily add our version in... + registry_manager.registry.AddAvailable(module); + + return module; + } + private static int printMissingInstanceError(IUser user) { user.RaiseMessage("I don't know where KSP is installed."); diff --git a/Cmdline/Options.cs b/Cmdline/Options.cs index 411e19ffbf..578b48a44d 100644 --- a/Cmdline/Options.cs +++ b/Cmdline/Options.cs @@ -82,6 +82,9 @@ internal class Actions : VerbCommandOptions [VerbOption("repair", HelpText = "Attempt various automatic repairs")] public SubCommandOptions Repair { get; set; } + [VerbOption("replace", HelpText = "Replace list of replaceable mods")] + public ReplaceOptions Replace { get; set; } + [VerbOption("repo", HelpText = "Manage CKAN repositories")] public SubCommandOptions Repo { get; set; } @@ -381,7 +384,34 @@ internal class UpgradeOptions : InstanceSpecificOptions public List modules { get; set; } } - internal class ScanOptions : InstanceSpecificOptions { } + internal class ReplaceOptions : InstanceSpecificOptions + { + [Option('c', "ckanfile", HelpText = "Local CKAN file to process")] + public string ckan_file { get; set; } + + [Option("no-recommends", HelpText = "Do not install recommended modules")] + public bool no_recommends { get; set; } + + [Option("with-suggests", HelpText = "Install suggested modules")] + public bool with_suggests { get; set; } + + [Option("with-all-suggests", HelpText = "Install suggested modules all the way down")] + public bool with_all_suggests { get; set; } + + [Option("allow-incompatible", DefaultValue = false, HelpText = "Install modules that are not compatible with the current game version")] + public bool allow_incompatible { get; set; } + + [Option("all", HelpText = "Replace all available replaced modules")] + public bool replace_all { get; set; } + + // TODO: How do we provide helptext on this? + [ValueList(typeof (List))] + public List modules { get; set; } + } + + internal class ScanOptions : InstanceSpecificOptions + { + } internal class ListOptions : InstanceSpecificOptions { diff --git a/ConsoleUI/DependencyScreen.cs b/ConsoleUI/DependencyScreen.cs index 254e45b435..c033829d3f 100644 --- a/ConsoleUI/DependencyScreen.cs +++ b/ConsoleUI/DependencyScreen.cs @@ -31,6 +31,7 @@ public DependencyScreen(KSPManager mgr, ChangePlan cp, HashSet rej, bool )); generateList(plan.Install); + generateList(new HashSet(ReplacementIdentifiers(plan.Replace))); dependencyList = new ConsoleListBox( 1, 4, -1, -2, @@ -137,6 +138,18 @@ private void generateList(HashSet inst) } } + private IEnumerable ReplacementIdentifiers(IEnumerable replaced_identifiers) + { + foreach (string replaced in replaced_identifiers) { + ModuleReplacement repl = registry.GetReplacement( + replaced, manager.CurrentInstance.VersionCriteria() + ); + if (repl != null) { + yield return repl.ReplaceWith.identifier; + } + } + } + private void AddDependencies(HashSet alreadyInstalling, string identifier, List source, bool installByDefault) { if (source != null) { diff --git a/ConsoleUI/InstallScreen.cs b/ConsoleUI/InstallScreen.cs index 1d565b1bad..e61a7ff678 100644 --- a/ConsoleUI/InstallScreen.cs +++ b/ConsoleUI/InstallScreen.cs @@ -71,6 +71,10 @@ public override void Run(Action process = null) inst.InstallList(iList, resolvOpts, dl); plan.Install.Clear(); } + if (plan.Replace.Count > 0) { + inst.Replace(AllReplacements(plan.Replace), resolvOpts, dl, true); + } + trans.Complete(); // Don't let the installer re-use old screen references inst.User = null; @@ -136,6 +140,20 @@ private void OnModInstalled(CkanModule mod) RaiseMessage($"{Symbols.checkmark} Successfully installed {mod.name} {Formatting.StripEpoch(mod.version)}"); } + private IEnumerable AllReplacements(IEnumerable identifiers) + { + IRegistryQuerier registry = RegistryManager.Instance(manager.CurrentInstance).registry; + + foreach (string id in identifiers) { + ModuleReplacement repl = registry.GetReplacement( + id, manager.CurrentInstance.VersionCriteria() + ); + if (repl != null) { + yield return repl; + } + } + } + private static readonly RelationshipResolverOptions resolvOpts = new RelationshipResolverOptions() { with_all_suggests = false, with_suggests = false, diff --git a/ConsoleUI/ModInfoScreen.cs b/ConsoleUI/ModInfoScreen.cs index 3be4cd81ed..7b9ff347a8 100644 --- a/ConsoleUI/ModInfoScreen.cs +++ b/ConsoleUI/ModInfoScreen.cs @@ -299,14 +299,45 @@ private int addVersionDisplay() if (latestIsInstalled) { - addVersionBox( - boxLeft, boxTop, boxRight, boxTop + boxH - 1, - () => $"Latest/Installed {instTime.ToString("d")}", - () => ConsoleTheme.Current.ActiveFrameFg, - true, - new List() {inst} + ModuleReplacement mr = registry.GetReplacement( + mod.identifier, + manager.CurrentInstance.VersionCriteria() ); - boxTop += boxH; + + if (mr != null) { + + // Show replaced_by + addVersionBox( + boxLeft, boxTop, boxRight, boxTop + boxH - 1, + () => $"Replaced by {mr.ReplaceWith.identifier}", + () => ConsoleTheme.Current.AlertFrameFg, + false, + new List() {mr.ReplaceWith} + ); + boxTop += boxH; + + addVersionBox( + boxLeft, boxTop, boxRight, boxTop + boxH - 1, + () => $"Installed {instTime.ToString("d")}", + () => ConsoleTheme.Current.ActiveFrameFg, + true, + new List() {inst} + ); + boxTop += boxH; + + } else { + + addVersionBox( + boxLeft, boxTop, boxRight, boxTop + boxH - 1, + () => $"Latest/Installed {instTime.ToString("d")}", + () => ConsoleTheme.Current.ActiveFrameFg, + true, + new List() {inst} + ); + boxTop += boxH; + + } + } else { diff --git a/ConsoleUI/ModListHelpDialog.cs b/ConsoleUI/ModListHelpDialog.cs index d011a46923..1708961e82 100644 --- a/ConsoleUI/ModListHelpDialog.cs +++ b/ConsoleUI/ModListHelpDialog.cs @@ -14,7 +14,7 @@ public class ModListHelpDialog : ConsoleDialog { /// public ModListHelpDialog() : base() { - SetDimensions(9, 4, -9, -3); + SetDimensions(9, 3, -9, -3); int btnW = 10; int btnL = (Console.WindowWidth - btnW) / 2; @@ -31,6 +31,7 @@ public ModListHelpDialog() : base() symbolTb.AddLine("=============="); symbolTb.AddLine($"{installed} Installed"); symbolTb.AddLine($"{upgradable} Upgradeable"); + symbolTb.AddLine($"{replaceable} Replaceable"); symbolTb.AddLine($"! Unavailable"); symbolTb.AddLine(" "); symbolTb.AddLine("Basic Keys"); @@ -65,6 +66,7 @@ public ModListHelpDialog() : base() private static readonly string installed = Symbols.checkmark; private static readonly string upgradable = Symbols.greaterEquals; + private static readonly string replaceable = Symbols.doubleGreater; } } diff --git a/ConsoleUI/ModListScreen.cs b/ConsoleUI/ModListScreen.cs index dab595216c..9da943cbe7 100644 --- a/ConsoleUI/ModListScreen.cs +++ b/ConsoleUI/ModListScreen.cs @@ -171,6 +171,10 @@ public ModListScreen(KSPManager mgr, bool dbg) () => moduleList.Selection != null && registry.HasUpdate(moduleList.Selection.identifier, manager.CurrentInstance.VersionCriteria()) ); + moduleList.AddTip("+", "Replace", + () => moduleList.Selection != null + && registry.GetReplacement(moduleList.Selection.identifier, manager.CurrentInstance.VersionCriteria()) != null + ); moduleList.AddBinding(Keys.Plus, (object sender) => { if (moduleList.Selection != null) { if (!registry.IsInstalled(moduleList.Selection.identifier, false)) { @@ -178,6 +182,8 @@ public ModListScreen(KSPManager mgr, bool dbg) } else if (registry.IsInstalled(moduleList.Selection.identifier, false) && registry.HasUpdate(moduleList.Selection.identifier, manager.CurrentInstance.VersionCriteria())) { plan.ToggleUpgrade(moduleList.Selection.identifier); + } else if (registry.GetReplacement(moduleList.Selection.identifier, manager.CurrentInstance.VersionCriteria()) != null) { + plan.ToggleReplace(moduleList.Selection.identifier); } } return true; @@ -520,6 +526,8 @@ public static string StatusSymbol(InstallStatus st) case InstallStatus.Installed: return installed; case InstallStatus.Installing: return installing; case InstallStatus.NotInstalled: return notinstalled; + case InstallStatus.Replaceable: return replaceable; + case InstallStatus.Replacing: return replacing; default: return ""; } } @@ -554,6 +562,8 @@ private long totalInstalledDownloadSize() private static readonly string upgradable = Symbols.greaterEquals; private static readonly string upgrading = "^"; private static readonly string removing = "-"; + private static readonly string replaceable = Symbols.doubleGreater; + private static readonly string replacing = Symbols.plusMinus; } /// @@ -599,12 +609,22 @@ public void ToggleUpgrade(string identifier) toggleContains(Upgrade, identifier); } + /// + /// Add or remove a mod from the replace list + /// + /// The mod to Replace + public void ToggleReplace(string identifier) + { + Remove.Remove(identifier); + toggleContains(Replace, identifier); + } + /// /// Return true if we are planning to make any changes, false otherwise /// public bool NonEmpty() { - return Install.Count > 0 || Upgrade.Count > 0 || Remove.Count > 0; + return Install.Count > 0 || Upgrade.Count > 0 || Remove.Count > 0 || Replace.Count > 0; } /// @@ -640,6 +660,10 @@ public InstallStatus GetModStatus(KSPManager manager, IRegistryQuerier registry, } else { return InstallStatus.Upgradeable; } + } else if (Replace.Contains(identifier)) { + return InstallStatus.Replacing; + } else if (registry.GetReplacement(identifier, manager.CurrentInstance.VersionCriteria()) != null) { + return InstallStatus.Replaceable; } else if (!IsAnyAvailable(registry, identifier)) { return InstallStatus.Unavailable; } else { @@ -702,6 +726,11 @@ public static void toggleContains(HashSet list, string identifier) /// Mods we're planning to remove /// public readonly HashSet Remove = new HashSet(); + + /// + /// Mods we're planning to replace with successor mods + /// + public readonly HashSet Replace = new HashSet(); } /// @@ -743,5 +772,16 @@ public enum InstallStatus { /// This mod is installed and we are planning to upgrade it /// Upgrading, + + /// + /// This mod is installed and can be replaced by a successor mod + /// + Replaceable, + + /// + /// This mod is installed and we are planning to replace it + /// + Replacing, + }; } diff --git a/ConsoleUI/Toolkit/Symbols.cs b/ConsoleUI/Toolkit/Symbols.cs index 176fe387d0..d64e08ba81 100644 --- a/ConsoleUI/Toolkit/Symbols.cs +++ b/ConsoleUI/Toolkit/Symbols.cs @@ -26,6 +26,15 @@ public static class Symbols { /// >= symbol /// public static readonly string greaterEquals = cp437s(0xf2); + /// + /// +- symbol + /// + public static readonly string plusMinus = cp437s(0xf1); + /// + /// >> symbol + /// + public static readonly string doubleGreater = cp437s(0xaf); + /// /// Hashed square box for drawing scrollbars diff --git a/Core/ModuleInstaller.cs b/Core/ModuleInstaller.cs index e258b0759c..aec0970329 100644 --- a/Core/ModuleInstaller.cs +++ b/Core/ModuleInstaller.cs @@ -1107,6 +1107,99 @@ public void Upgrade(IEnumerable modules, NetAsyncModulesDownloader n ); } + /// + /// Enacts listed Module Replacements 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. + /// + public void Replace(IEnumerable replacements, RelationshipResolverOptions options, NetAsyncModulesDownloader netAsyncDownloader, bool enforceConsistency = true) + { + log.Debug("Using Replace method"); + List modsToInstall = new List(); + var modsToRemove = new List(); + foreach (ModuleReplacement repl in replacements) + { + modsToInstall.Add(repl.ReplaceWith); + log.DebugFormat("We want to install {0} as a replacement for {1}", repl.ReplaceWith.identifier, repl.ToReplace.identifier); + } + // Start by making sure we've downloaded everything. + DownloadModules(modsToInstall, netAsyncDownloader); + + // Our replacement involves removing the currently installed mods, then + // adding everything that needs installing (which may involve new mods to + // satisfy dependencies). + + + // Let's discover what we need to do with each module! + foreach (ModuleReplacement repl in replacements) + { + string ident = repl.ToReplace.identifier; + InstalledModule installedMod = registry_manager.registry.InstalledModule(ident); + + if (installedMod == null) + { + log.DebugFormat("Wait, {0} is not actually installed?", installedMod.identifier); + //Maybe ModuleNotInstalled ? + if (registry_manager.registry.IsAutodetected(ident)) + { + throw new ModuleNotFoundKraken(ident, repl.ToReplace.version.ToString(), String.Format("Can't replace {0} as it was not installed by CKAN. \r\n Please remove manually before trying to install it.", ident)); + } + + throw new ModuleNotFoundKraken(ident, repl.ToReplace.version.ToString(), String.Format("Can't replace {0} as it is not installed. Please attempt to install {1} instead.", ident, repl.ReplaceWith.identifier)); + } + else + { + // Obviously, we need to remove the mod we are replacing + modsToRemove.Add(repl.ToReplace.identifier); + + log.DebugFormat("Ok, we are removing {0}", repl.ToReplace.identifier); + //Check whether our Replacement target is already installed + InstalledModule installed_replacement = registry_manager.registry.InstalledModule(repl.ReplaceWith.identifier); + + // If replacement is not installed, we've already added it to modsToInstall above + if (installed_replacement != null) + { + //Module already installed. We'll need to treat it as an upgrade. + log.DebugFormat("It turns out {0} is already installed, we'll upgrade it.", installed_replacement.identifier); + modsToRemove.Add(installed_replacement.identifier); + + CkanModule installed = installed_replacement.Module; + if (installed.version.IsEqualTo(repl.ReplaceWith.version)) + { + log.InfoFormat("{0} is already at the latest version, reinstalling to replace {1}", repl.ReplaceWith.identifier, repl.ToReplace.identifier); + } + else if (installed.version.IsGreaterThan(repl.ReplaceWith.version)) + { + log.WarnFormat("Downgrading {0} from {1} to {2} to replace {3}", repl.ReplaceWith.identifier, repl.ReplaceWith.version, repl.ReplaceWith.version, repl.ToReplace.identifier); + } + else + { + log.InfoFormat("Upgrading {0} to {1} to replace {2}", repl.ReplaceWith.identifier, repl.ReplaceWith.version, repl.ToReplace.identifier); + } + } + else + { + log.InfoFormat("Replacing {0} with {1} {2}", repl.ToReplace.identifier, repl.ReplaceWith.identifier, repl.ReplaceWith.version); + } + } + } + var resolver = new RelationshipResolver(modsToInstall, options, registry_manager.registry, ksp.VersionCriteria()); + try + { + var resolvedModsToInstall = resolver.ModList().ToList(); + AddRemove( + resolvedModsToInstall, + modsToRemove, + enforceConsistency + ); + } + catch (DependencyNotSatisfiedKraken kraken) + { + throw kraken; + } + + } + #endregion /// diff --git a/Core/Registry/AvailableModule.cs b/Core/Registry/AvailableModule.cs index 3db3d2c350..ff8d30d057 100644 --- a/Core/Registry/AvailableModule.cs +++ b/Core/Registry/AvailableModule.cs @@ -109,7 +109,7 @@ public CkanModule Latest(KspVersionCriteria ksp_version = null, RelationshipDesc return module_version[version]; log.DebugFormat("No version of {0} is compatible with KSP {1}", - module_version[available_versions[0]].identifier, ksp_version); + module_version[available_versions[0]].identifier, ksp_version.ToString()); return null; } diff --git a/Core/Registry/IRegistryQuerier.cs b/Core/Registry/IRegistryQuerier.cs index 39ea0e9226..42fe770d38 100644 --- a/Core/Registry/IRegistryQuerier.cs +++ b/Core/Registry/IRegistryQuerier.cs @@ -116,7 +116,7 @@ public interface IRegistryQuerier /// Helpers for /// public static class IRegistryQuerierHelpers -{ + { /// /// Helper to call /// @@ -176,13 +176,76 @@ public static bool HasUpdate(this IRegistryQuerier querier, string identifier, K public static string CompatibleGameVersions(this IRegistryQuerier querier, string identifier) { List releases = querier.AllAvailable(identifier); - if (releases != null && releases.Count > 0) { - Version minMod = null, maxMod = null; + if (releases != null && releases.Count > 0) + { + Version minMod = null, maxMod = null; KspVersion minKsp = null, maxKsp = null; Registry.GetMinMaxVersions(releases, out minMod, out maxMod, out minKsp, out maxKsp); return KspVersionRange.VersionSpan(minKsp, maxKsp); } return ""; } + + /// + /// Given a mod identifier, return a ModuleReplacement containing the trelevant replacement + /// if compatibility matches. + /// + + public static ModuleReplacement GetReplacement( this IRegistryQuerier querier, string identifier, KspVersionCriteria version) + { + // We only care about the installed version + CkanModule installedVersion; + try + { + installedVersion = querier.GetInstalledVersion(identifier); + } + catch (ModuleNotFoundKraken) + { + return null; + } + if (installedVersion == null) return null; // Mod is not installed, so we don't care about replacements + //get the identifier from the replaced_by relationship, if it exists + if (installedVersion.replaced_by == null) return null; // No replaced_by relationship + + RelationshipDescriptor replacedBy = installedVersion.replaced_by; + + // Now we need to see if there is a compatible version of the replacement + try + { + ModuleReplacement replacement = new ModuleReplacement(); + replacement.ToReplace = installedVersion; + if (installedVersion.replaced_by.version != null) + { + replacement.ReplaceWith = querier.GetModuleByVersion(installedVersion.replaced_by.name, installedVersion.replaced_by.version); + if (replacement.ReplaceWith != null) + { + if (replacement.ReplaceWith.IsCompatibleKSP(version)) + { + return replacement; + } + } + } + else + { + replacement.ReplaceWith = querier.LatestAvailable(installedVersion.replaced_by.name, version); + if (replacement.ReplaceWith != null) + { + if (installedVersion.replaced_by.min_version != null) + { + if (!replacement.ReplaceWith.version.IsLessThan(replacedBy.min_version)) + { + return replacement; + } + } + else return replacement; + } + } + return null; + } + catch (ModuleNotFoundKraken) + { + return null; + } + } } } diff --git a/Core/Relationships/RelationshipResolver.cs b/Core/Relationships/RelationshipResolver.cs index 5a8e0fbb07..92f8eab9d9 100644 --- a/Core/Relationships/RelationshipResolver.cs +++ b/Core/Relationships/RelationshipResolver.cs @@ -612,6 +612,20 @@ public override string Reason } } + public class Replacement : SelectionReason + { + public Replacement(CkanModule module) + { + if (module == null) throw new ArgumentNullException(); + Parent = module; + } + + public override string Reason + { + get { return " Replacing " + Parent.name + ".\r\n"; } + } + } + public sealed class Suggested : SelectionReason { public Suggested(CkanModule module) diff --git a/Core/Types/CkanModule.cs b/Core/Types/CkanModule.cs index c55bdcb9af..9a0705e199 100644 --- a/Core/Types/CkanModule.cs +++ b/Core/Types/CkanModule.cs @@ -70,6 +70,12 @@ public string RequiredVersion } + public class ModuleReplacement + { + public CkanModule ToReplace; + public CkanModule ReplaceWith; + } + public class ResourcesDescriptor { [JsonProperty("repository", NullValueHandling = NullValueHandling.Ignore)] @@ -166,6 +172,9 @@ public class CkanModule : IEquatable [JsonProperty("depends", NullValueHandling = NullValueHandling.Ignore)] public List depends; + [JsonProperty("replaced_by", NullValueHandling = NullValueHandling.Ignore)] + public RelationshipDescriptor replaced_by; + [JsonProperty("download")] public Uri download; @@ -406,6 +415,7 @@ public static CkanModule FromIDandVersion(IRegistryQuerier registry, string mod, ); /// Generates a CKAN.Meta object given a filename + /// TODO: Catch and display errors public static CkanModule FromFile(string filename) { string json = File.ReadAllText(filename); @@ -467,7 +477,7 @@ internal static bool UniConflicts(CkanModule mod1, CkanModule mod2) /// public bool IsCompatibleKSP(KspVersionCriteria version) { - log.DebugFormat("Testing if {0} is compatible with KSP {1}", this, version); + log.DebugFormat("Testing if {0} is compatible with KSP {1}", this, version.ToString()); return _comparator.Compatible(version, this); diff --git a/Core/Versioning/KspVersionCriteria.cs b/Core/Versioning/KspVersionCriteria.cs index 3e53487ebd..da780bf64e 100644 --- a/Core/Versioning/KspVersionCriteria.cs +++ b/Core/Versioning/KspVersionCriteria.cs @@ -35,7 +35,12 @@ public IList Versions { public override String ToString() { - return "[Versions: " + _versions.ToString() + "]"; + List versionList = new List(); + foreach (KspVersion version in _versions) + { + versionList.Add(version.ToString()); + } + return "[Versions: " + String.Join( ", ", versionList) + "]"; } } } diff --git a/GUI/CKAN-GUI.csproj b/GUI/CKAN-GUI.csproj index bb6dad0692..9b8dc7e834 100644 --- a/GUI/CKAN-GUI.csproj +++ b/GUI/CKAN-GUI.csproj @@ -12,6 +12,8 @@ v4.5 512 false + ..\_build\out\$(AssemblyName)\$(Configuration)\bin\ + ..\_build\out\$(AssemblyName)\$(Configuration)\obj\ publish\ true Disk @@ -26,8 +28,6 @@ 1.0.0.0 false true - ..\_build\out\$(AssemblyName)\$(Configuration)\bin\ - ..\_build\out\$(AssemblyName)\$(Configuration)\obj\ AnyCPU @@ -319,6 +319,8 @@ PreserveNewest + + @@ -334,4 +336,4 @@ - + \ No newline at end of file diff --git a/GUI/Main.cs b/GUI/Main.cs index 3182ab8b6a..56047869e4 100644 --- a/GUI/Main.cs +++ b/GUI/Main.cs @@ -33,7 +33,8 @@ public enum GUIModChangeType None = 0, Install = 1, Remove = 2, - Update = 3 + Update = 3, + Replace = 4 } public partial class Main: Form diff --git a/GUI/MainChangeset.cs b/GUI/MainChangeset.cs index 728ab9e535..10f8cbeb63 100644 --- a/GUI/MainChangeset.cs +++ b/GUI/MainChangeset.cs @@ -23,7 +23,7 @@ public void UpdateChangesDialog(List changeset, BackgroundWorker inst } // We're going to split our change-set into two parts: updated/removed mods, - // and everything else (which right now is installing mods, but we may have + // and everything else (which right now is replacing and installing mods, but we may have // other types in the future). changeSet = new List(); diff --git a/GUI/MainModList.cs b/GUI/MainModList.cs index 9c695538c8..7f4feb61b8 100644 --- a/GUI/MainModList.cs +++ b/GUI/MainModList.cs @@ -276,6 +276,10 @@ protected override void SetSelectedRowCore(int rowIndex, bool selected) return; base.SetSelectedRowCore(rowIndex, selected); } + + //ImageList for Update/Changes Column + public System.Windows.Forms.ImageList ModChangesImageList { get; set; } + } public class MainModList @@ -554,11 +558,29 @@ public IEnumerable ConstructModList(IEnumerable modules selecting.ReadOnly = selecting is DataGridViewTextBoxCell; updating.ReadOnly = updating is DataGridViewTextBoxCell; + // TODO1888 : Add ContextMenuStrip Property for context menu and OnMouseHover to show selected action and conflict info + + //ToolStripMenuItem ModRightClickMenu = new ToolStripMenuItem(); + full_list_of_mod_rows.Add(mod.Identifier, item); } return full_list_of_mod_rows.Values; } + //TODO1888 Define ModContextMenu + + //TODO1888 Setup OnMouseHover over the Update/Change column + //Not sure if this is the right place to add this definition, + // see https://msdn.microsoft.com/en-us/library/system.windows.forms.datagridviewrow.contextmenustrip(v=vs.110).aspx + // private DataGridViewCellEventArgs mouseLocation; + +// private void dataGridView_CellMouseEnter(object sender, +// DataGridViewCellEventArgs location) +// { +// mouseLocation = location; +// } + + /// /// Returns a version string shorn of any leading epoch as delimited by a single colon /// diff --git a/GUI/Properties/Resources.Designer.cs b/GUI/Properties/Resources.Designer.cs index 3e608e48a5..36d522711f 100644 --- a/GUI/Properties/Resources.Designer.cs +++ b/GUI/Properties/Resources.Designer.cs @@ -10,8 +10,8 @@ namespace CKAN.Properties { using System; - - + + /// /// A strongly-typed resource class, for looking up localized strings, etc. /// @@ -23,15 +23,15 @@ namespace CKAN.Properties { [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { - + private static global::System.Resources.ResourceManager resourceMan; - + private static global::System.Globalization.CultureInfo resourceCulture; - + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal Resources() { } - + /// /// Returns the cached ResourceManager instance used by this class. /// @@ -45,7 +45,7 @@ internal Resources() { return resourceMan; } } - + /// /// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class. @@ -59,7 +59,17 @@ internal Resources() { resourceCulture = value; } } - + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap alert { + get { + object obj = ResourceManager.GetObject("alert", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// @@ -69,7 +79,27 @@ internal static System.Drawing.Bitmap apply { return ((System.Drawing.Bitmap)(obj)); } } - + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap arrow_forward { + get { + object obj = ResourceManager.GetObject("arrow_forward", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap arrow_upward { + get { + object obj = ResourceManager.GetObject("arrow_upward", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// @@ -79,7 +109,7 @@ internal static System.Drawing.Bitmap backward { return ((System.Drawing.Bitmap)(obj)); } } - + /// /// Looks up a localized string similar to CKAN metadata (*.ckan)|*.ckan. /// @@ -88,7 +118,7 @@ internal static string CKANFileFilter { return ResourceManager.GetString("CKANFileFilter", resourceCulture); } } - + /// /// Looks up a localized string similar to Export Mod List. /// @@ -97,7 +127,7 @@ internal static string ExportInstalledModsDialogTitle { return ResourceManager.GetString("ExportInstalledModsDialogTitle", resourceCulture); } } - + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// @@ -107,7 +137,7 @@ internal static System.Drawing.Bitmap filter { return ((System.Drawing.Bitmap)(obj)); } } - + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// @@ -117,7 +147,17 @@ internal static System.Drawing.Bitmap forward { return ((System.Drawing.Bitmap)(obj)); } } - + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap info { + get { + object obj = ResourceManager.GetObject("info", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// @@ -127,7 +167,7 @@ internal static System.Drawing.Bitmap ksp { return ((System.Drawing.Bitmap)(obj)); } } - + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// @@ -137,7 +177,7 @@ internal static System.Drawing.Bitmap refresh { return ((System.Drawing.Bitmap)(obj)); } } - + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// @@ -147,7 +187,7 @@ internal static System.Drawing.Bitmap search { return ((System.Drawing.Bitmap)(obj)); } } - + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// @@ -157,27 +197,17 @@ internal static System.Drawing.Bitmap settings { return ((System.Drawing.Bitmap)(obj)); } } - + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// - internal static System.Drawing.Bitmap textClear { - get { - object obj = ResourceManager.GetObject("textClear", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - internal static System.Drawing.Bitmap update { + internal static System.Drawing.Bitmap smile { get { - object obj = ResourceManager.GetObject("update", resourceCulture); + object obj = ResourceManager.GetObject("smile", resourceCulture); return ((System.Drawing.Bitmap)(obj)); } } - + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// @@ -187,43 +217,33 @@ internal static System.Drawing.Bitmap star { return ((System.Drawing.Bitmap)(obj)); } } - + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// - internal static System.Drawing.Bitmap thumbup { - get { - object obj = ResourceManager.GetObject("thumbup", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } - } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - internal static System.Drawing.Bitmap info { + internal static System.Drawing.Bitmap textClear { get { - object obj = ResourceManager.GetObject("info", resourceCulture); + object obj = ResourceManager.GetObject("textClear", resourceCulture); return ((System.Drawing.Bitmap)(obj)); } } - + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// - internal static System.Drawing.Bitmap smile { + internal static System.Drawing.Bitmap thumbup { get { - object obj = ResourceManager.GetObject("smile", resourceCulture); + object obj = ResourceManager.GetObject("thumbup", resourceCulture); return ((System.Drawing.Bitmap)(obj)); } } - + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// - internal static System.Drawing.Bitmap alert { + internal static System.Drawing.Bitmap update { get { - object obj = ResourceManager.GetObject("alert", resourceCulture); + object obj = ResourceManager.GetObject("update", resourceCulture); return ((System.Drawing.Bitmap)(obj)); } } diff --git a/GUI/Properties/Resources.resx b/GUI/Properties/Resources.resx index f6dfb95885..4d12c26674 100644 --- a/GUI/Properties/Resources.resx +++ b/GUI/Properties/Resources.resx @@ -1,17 +1,17 @@ - @@ -169,4 +169,10 @@ ..\Resources\alert.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - + + ..\Resources\arrow_forward.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Resources\arrow_upward.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/GUI/Resources/arrow_forward.png b/GUI/Resources/arrow_forward.png new file mode 100644 index 0000000000..47148c2ccd Binary files /dev/null and b/GUI/Resources/arrow_forward.png differ diff --git a/GUI/Resources/arrow_upward.png b/GUI/Resources/arrow_upward.png new file mode 100644 index 0000000000..d30a33a463 Binary files /dev/null and b/GUI/Resources/arrow_upward.png differ diff --git a/Spec.md b/Spec.md index 309328d3e4..f1193ef2f9 100644 --- a/Spec.md +++ b/Spec.md @@ -479,7 +479,23 @@ only. A list of mods which *conflict* with this mod. The current mod *will not* be installed if any of these mods are already on the system. -##### resources +##### replaced-by + +(**v1.24**) This is a way to mark a specific mod identifier as being +obsoleted and tell the client what it has been *replaced by*. It contains a +single mod that should be selected for installation if a replace command is +performed on this mod, while this mod is uninstalled. If this mod identifier +is brought back to life, an epoch change should be applied. A *replaced_by* +relationship should be added to the final release of the mod being replaced. +The listed mod should include a "provides" relationship either to this mod, +or one of this mod's listed "provides". + +replaced_by differs from other relationships in two ways: + +- It is *not* an array. Only a single mod can be defined as the replacement. +- Only "version" and "min_version" are permitted as options. + +#### resources The `resources` field describes additional information that a user or program may wish to know about the mod, but which are not required diff --git a/Tests/Core/ModuleInstaller.cs b/Tests/Core/ModuleInstaller.cs index 64420cd8fb..1ead63d1bf 100644 --- a/Tests/Core/ModuleInstaller.cs +++ b/Tests/Core/ModuleInstaller.cs @@ -647,6 +647,24 @@ public void AllowInstallsToScenarios() } } + [Test] + public void SuccessfulReplacement() + { + //Need to set up an installed DogeCoinFlag-101replaced mod that can validly be replaced by DogeTokenFlag-101 + + // Assert that DogeCoinFlag has been removed and DogeTokenFlag has been installed + Assert.IsTrue(true); + } + + [Test] + public void UnsuccessfulReplacement() + { + //Need to set up an installed DogeCoinFlag-101-replaced mod in a KSP version too low for DogeTokenFlag-101 + + // Assert that DogeCoinFlag has not been removed and DogeTokenFlag has not been installed + Assert.IsTrue(true); + } + private static void TestDogeCoinStanza(ModuleInstallDescriptor stanza) { diff --git a/Tests/Core/Versioning/KspVersionBoundTests.cs b/Tests/Core/Versioning/KspVersionBoundTests.cs index 1152d81b3f..9cb8855858 100644 --- a/Tests/Core/Versioning/KspVersionBoundTests.cs +++ b/Tests/Core/Versioning/KspVersionBoundTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using CKAN.Versioning; using NUnit.Framework; diff --git a/Tests/Core/Versioning/KspVersionJsonConverterTests.cs b/Tests/Core/Versioning/KspVersionJsonConverterTests.cs index 79267f0085..f32cf0ff7b 100644 --- a/Tests/Core/Versioning/KspVersionJsonConverterTests.cs +++ b/Tests/Core/Versioning/KspVersionJsonConverterTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using CKAN.Versioning; using Newtonsoft.Json; using NUnit.Framework; diff --git a/Tests/Core/Versioning/KspVersionTests.cs b/Tests/Core/Versioning/KspVersionTests.cs index d3291723f6..df921ff72b 100644 --- a/Tests/Core/Versioning/KspVersionTests.cs +++ b/Tests/Core/Versioning/KspVersionTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using CKAN.Versioning; using NUnit.Framework; diff --git a/Tests/Data/DogeTokenFlag-1.01.zip b/Tests/Data/DogeTokenFlag-1.01.zip new file mode 100644 index 0000000000..bd83ebd2df Binary files /dev/null and b/Tests/Data/DogeTokenFlag-1.01.zip differ diff --git a/Tests/Data/TestData.cs b/Tests/Data/TestData.cs index c7916008ed..191d655e84 100644 --- a/Tests/Data/TestData.cs +++ b/Tests/Data/TestData.cs @@ -148,6 +148,79 @@ public static string DogeCoinFlag_101() "; } + /// + /// Replaced_by DogeCoinFlag 1.01 info. This doesn't contain any bugs. + /// + public static string DogeCoinFlag_101_replaced() + { + return @" + { + ""spec_version"": ""v1.24"", + ""identifier"": ""DogeCoinFlag"", + ""install"": [ + { + ""file"": ""DogeCoinFlag-1.01/GameData/DogeCoinFlag"", + ""install_to"": ""GameData"", + ""filter"" : [ ""Thumbs.db"", ""README.md"" ], + ""filter_regexp"" : ""\\.bak$"" + } + ], + ""replaced_by"": { + ""name"": ""DogeTokenFlag"", + ""min_version"": ""1.01"" + ), + ""resources"": { + ""kerbalstuff"": { + ""url"": ""https://kerbalstuff.com/mod/269/Dogecoin%20Flag"" + }, + ""homepage"": ""https://www.reddit.com/r/dogecoin/comments/1tdlgg/i_made_a_more_accurate_dogecoin_and_a_ksp_flag/"" + }, + ""name"": ""Dogecoin Flag"", + ""license"": ""CC-BY"", + ""abstract"": ""Such flag. Very currency. To the mun! Wow!"", + ""author"": ""pjf"", + ""version"": ""1.01"", + ""download"": ""https://kerbalstuff.com/mod/269/Dogecoin%20Flag/download/1.01"", + ""comment"": ""Generated by ks2ckan"", + ""download_size"": 53647, + ""ksp_version"": ""0.24"" + } + "; + } + + /// + /// DogeTokenFlag 1.01 info. This is our replacement target. + /// + public static string DogeTokenFlag_101() + { + return @" + { + ""spec_version"": 1, + ""identifier"": ""DogeTokenFlag"", + ""install"": [ + { + ""file"": ""DogeTokenFlag-1.01/GameData/DogeTokenFlag"", + ""install_to"": ""GameData"", + ""filter"" : [ ""Thumbs.db"", ""README.md"" ], + ""filter_regexp"" : ""\\.bak$"" + } + ], + ""resources"": { + ""homepage"": ""https://www.reddit.com/r/dogecoin/comments/1tdlgg/i_made_a_more_accurate_dogecoin_and_a_ksp_flag/"" + }, + ""name"": ""Dogetoken Flag"", + ""license"": ""CC-BY"", + ""abstract"": ""Such flag. Very token. To the mun! Wow!"", + ""author"": ""politas"", + ""version"": ""1.01"", + ""download"": ""https://kerbalstuff.com/mod/269/Dogetoken%20Flag/download/1.01"", + ""comment"": ""Generated by hand"", + ""download_size"": 53647, + ""ksp_version"": ""0.25"" + } + "; + } + public static CkanModule DogeCoinFlag_101_module() { return CkanModule.FromJson(DogeCoinFlag_101()); diff --git a/Tests/NetKAN/NetkanOverride.cs b/Tests/NetKAN/NetkanOverride.cs index 480e894036..7bfdc25763 100644 --- a/Tests/NetKAN/NetkanOverride.cs +++ b/Tests/NetKAN/NetkanOverride.cs @@ -1,4 +1,4 @@ -using CKAN.NetKAN.Model; +using CKAN.NetKAN.Model; using CKAN.NetKAN.Transformers; using Newtonsoft.Json.Linq; using NUnit.Framework; diff --git a/Tests/NetKAN/Services/FileServiceTests.cs b/Tests/NetKAN/Services/FileServiceTests.cs index 06c3ac339d..58b6fbcf5b 100644 --- a/Tests/NetKAN/Services/FileServiceTests.cs +++ b/Tests/NetKAN/Services/FileServiceTests.cs @@ -1,4 +1,4 @@ -using System.IO; +using System.IO; using CKAN.NetKAN.Services; using NUnit.Framework; using Tests.Data; diff --git a/Tests/NetKAN/Services/ModuleServiceTests.cs b/Tests/NetKAN/Services/ModuleServiceTests.cs index a60b22c5d8..e731bc2b81 100644 --- a/Tests/NetKAN/Services/ModuleServiceTests.cs +++ b/Tests/NetKAN/Services/ModuleServiceTests.cs @@ -1,4 +1,4 @@ -using CKAN; +using CKAN; using CKAN.NetKAN.Services; using CKAN.Versioning; using Newtonsoft.Json.Linq; diff --git a/Tests/NetKAN/Transformers/AvcTransformerTests.cs b/Tests/NetKAN/Transformers/AvcTransformerTests.cs index eb332e161b..dd669ae389 100644 --- a/Tests/NetKAN/Transformers/AvcTransformerTests.cs +++ b/Tests/NetKAN/Transformers/AvcTransformerTests.cs @@ -1,4 +1,4 @@ -using CKAN; +using CKAN; using CKAN.NetKAN.Model; using CKAN.NetKAN.Services; using CKAN.NetKAN.Sources.Avc; diff --git a/Tests/NetKAN/Transformers/GithubTransformerTests.cs b/Tests/NetKAN/Transformers/GithubTransformerTests.cs index e84c4fb816..c1a576dfe6 100644 --- a/Tests/NetKAN/Transformers/GithubTransformerTests.cs +++ b/Tests/NetKAN/Transformers/GithubTransformerTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using CKAN.NetKAN.Model; using CKAN.NetKAN.Sources.Github; using CKAN.NetKAN.Transformers; diff --git a/Tests/NetKAN/Transformers/HttpTransformerTests.cs b/Tests/NetKAN/Transformers/HttpTransformerTests.cs index 6cc8b71063..9ad207943e 100644 --- a/Tests/NetKAN/Transformers/HttpTransformerTests.cs +++ b/Tests/NetKAN/Transformers/HttpTransformerTests.cs @@ -1,4 +1,4 @@ -using CKAN.NetKAN.Model; +using CKAN.NetKAN.Model; using CKAN.NetKAN.Transformers; using Newtonsoft.Json.Linq; using NUnit.Framework; diff --git a/Tests/NetKAN/Transformers/VersionEditTransformerTests.cs b/Tests/NetKAN/Transformers/VersionEditTransformerTests.cs index 9a29ad7e19..69952cd588 100644 --- a/Tests/NetKAN/Transformers/VersionEditTransformerTests.cs +++ b/Tests/NetKAN/Transformers/VersionEditTransformerTests.cs @@ -1,4 +1,4 @@ -using CKAN; +using CKAN; using CKAN.NetKAN.Model; using CKAN.NetKAN.Transformers; using Newtonsoft.Json.Linq; diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index e47724d16d..476a2bad1d 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -1,5 +1,5 @@  - + Debug @@ -33,9 +33,6 @@ false - - ..\_build\lib\nuget\Castle.Core.4.2.1\lib\net45\Castle.Core.dll - ..\lib\curlsharp-v0.5.1-2-gd2d5699\CurlSharp.dll @@ -43,23 +40,28 @@ ..\_build\lib\nuget\ICSharpCode.SharpZipLib.Patched.0.86.5.1\lib\net20\ICSharpCode.SharpZipLib.dll True - + ..\_build\lib\nuget\log4net.2.0.8\lib\net45-full\log4net.dll ..\_build\lib\nuget\Moq.4.7.145\lib\net45\Moq.dll + False ..\_build\lib\nuget\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll ..\_build\lib\nuget\NUnit.3.9.0\lib\net45\nunit.framework.dll + False + + ..\_build\lib\nuget\Castle.Core.4.2.1\lib\net45\Castle.Core.dll + @@ -128,23 +130,24 @@ + + + + + + + + + - - - - - - - - @@ -166,15 +169,7 @@ - - - - - - - - - +