diff --git a/.gitignore b/.gitignore
index 3840cfad32..3a2fa46d1d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
build/
+/.vs/
packages/
CKAN/*/bin
CKAN/*/obj
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f52626e4c6..98923957ff 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ All notable changes to this project will be documented in this file.
### Internal
+- [Core] CKAN will now read BuildID.txt for more accurate KSP versions (#1645 by: dbent)
- [Multiple] Removed various references and code for processing mods on KerbalStuff. Thank you, Sircmpwn, for providing us with such a great service for so long. (#1615 by: Olympic1; reviewed: pjf)
- [Spec] Updated Spec with the `kind` field which was introduced in v1.6. (#1597 by: plague006; reviewed: Daz)
- [spec] ckan.schema now enforces structure of install directives (#1578 by: Zane6888; reviewed: pjf, Daz)
diff --git a/Core/CKAN-core.csproj b/Core/CKAN-core.csproj
index be1c49ef6e..b22c605d0c 100644
--- a/Core/CKAN-core.csproj
+++ b/Core/CKAN-core.csproj
@@ -13,6 +13,7 @@
bin\Debug
4
true
+ 5
@@ -50,6 +51,11 @@
+
+
+
+
+
@@ -61,7 +67,6 @@
-
@@ -83,6 +88,10 @@
+
+
+
+
@@ -99,10 +108,9 @@
+
-
-
-
-
+
+
\ No newline at end of file
diff --git a/Core/GameVersionProviders/IGameVersionProvider.cs b/Core/GameVersionProviders/IGameVersionProvider.cs
new file mode 100644
index 0000000000..96204ec4eb
--- /dev/null
+++ b/Core/GameVersionProviders/IGameVersionProvider.cs
@@ -0,0 +1,9 @@
+using CKAN.Versioning;
+
+namespace CKAN.GameVersionProviders
+{
+ public interface IGameVersionProvider
+ {
+ bool TryGetVersion(string directory, out KspVersion result);
+ }
+}
diff --git a/Core/GameVersionProviders/IKspBuildMap.cs b/Core/GameVersionProviders/IKspBuildMap.cs
new file mode 100644
index 0000000000..549441bee2
--- /dev/null
+++ b/Core/GameVersionProviders/IKspBuildMap.cs
@@ -0,0 +1,11 @@
+using CKAN.Versioning;
+
+namespace CKAN.GameVersionProviders
+{
+ public interface IKspBuildMap
+ {
+ KspVersion this[string buildId] { get; }
+
+ void Refresh();
+ }
+}
diff --git a/Core/GameVersionProviders/KspBuildIdVersionProvider.cs b/Core/GameVersionProviders/KspBuildIdVersionProvider.cs
new file mode 100644
index 0000000000..5e1639aa58
--- /dev/null
+++ b/Core/GameVersionProviders/KspBuildIdVersionProvider.cs
@@ -0,0 +1,49 @@
+using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+using CKAN.Versioning;
+
+namespace CKAN.GameVersionProviders
+{
+ // ReSharper disable once ClassNeverInstantiated.Global
+ public sealed class KspBuildIdVersionProvider : IGameVersionProvider
+ {
+ private static readonly Regex BuildIdPattern = new Regex(@"^build id\s+=\s+0*(?\d+)",
+ RegexOptions.IgnoreCase | RegexOptions.Compiled
+ );
+
+ private readonly IKspBuildMap _kspBuildMap;
+
+ public KspBuildIdVersionProvider(IKspBuildMap kspBuildMap)
+ {
+ _kspBuildMap = kspBuildMap;
+ }
+
+ public bool TryGetVersion(string directory, out KspVersion result)
+ {
+ var buildIdPath = Path.Combine(directory, "buildID.txt");
+
+ if (File.Exists(buildIdPath))
+ {
+ var match = File
+ .ReadAllLines(buildIdPath)
+ .Select(i => BuildIdPattern.Match(i))
+ .FirstOrDefault(i => i.Success);
+
+ if (match != null)
+ {
+ var version = _kspBuildMap[match.Groups["buildid"].Value];
+
+ if (version != null)
+ {
+ result = version;
+ return true;
+ }
+ }
+ }
+
+ result = default(KspVersion);
+ return false;
+ }
+ }
+}
diff --git a/Core/GameVersionProviders/KspBuildMap.cs b/Core/GameVersionProviders/KspBuildMap.cs
new file mode 100644
index 0000000000..f85c53f374
--- /dev/null
+++ b/Core/GameVersionProviders/KspBuildMap.cs
@@ -0,0 +1,175 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using CKAN.Versioning;
+using log4net;
+using Newtonsoft.Json;
+
+namespace CKAN.GameVersionProviders
+{
+ // ReSharper disable once ClassNeverInstantiated.Global
+ public sealed class KspBuildMap : IKspBuildMap
+ {
+ // TODO: Need a way for the client to configure this
+ private static readonly Uri BuildMapUri =
+ new Uri("https://raw.githubusercontent.com/KSP-CKAN/CKAN-meta/master/builds.json");
+
+ // TODO: Get this through dependency injection
+ private readonly ILog _log = LogManager.GetLogger(typeof(KspBuildMap));
+
+ private readonly object _buildMapLock = new object();
+ private JBuilds _jBuilds;
+
+ private readonly IWin32Registry _registry;
+
+ public KspVersion this[string buildId]
+ {
+ get
+ {
+ EnsureBuildMap();
+
+ string version;
+ return _jBuilds.Builds.TryGetValue(buildId, out version) ? KspVersion.Parse(version) : null;
+ }
+ }
+
+ public KspBuildMap(IWin32Registry registry)
+ {
+ _registry = registry;
+ }
+
+ private void EnsureBuildMap()
+ {
+ if (ReferenceEquals(_jBuilds, null))
+ {
+ lock(_buildMapLock)
+ {
+ if (ReferenceEquals(_jBuilds, null))
+ {
+ Refresh(useCachedVersion: true);
+ }
+ }
+ }
+ }
+
+ public void Refresh()
+ {
+ Refresh(useCachedVersion: false);
+ }
+
+ private void Refresh(bool useCachedVersion)
+ {
+ if (useCachedVersion)
+ {
+ // Attempt to set the build map from the cached version in the registry
+ if (TrySetRegistryBuildMap()) return;
+
+ // Attempt to set the build map from the repository
+ if (TrySetRemoteBuildMap()) return;
+ }
+ else
+ {
+ // Attempt to set the build map from the repository
+ if (TrySetRemoteBuildMap()) return;
+
+ // Attempt to set the build map from the cached version in the registry
+ if (TrySetRegistryBuildMap()) return;
+ }
+
+ // If that fails attempt to set the build map from the embedded version
+ if (TrySetEmbeddedBuildMap()) return;
+
+ _log.Warn("Could not refresh the build map from any source");
+ }
+
+ private bool TrySetBuildMap(string buildMapJson)
+ {
+ try
+ {
+ _jBuilds = JsonConvert.DeserializeObject(buildMapJson);
+ return true;
+ }
+ catch(Exception e)
+ {
+ _log.WarnFormat("Could not parse build map");
+ _log.DebugFormat("{0}\n{1}", buildMapJson, e);
+ return false;
+ }
+ }
+
+ private bool TrySetRemoteBuildMap()
+ {
+ try
+ {
+ var json = Net.DownloadText(BuildMapUri);
+
+ if (TrySetBuildMap(json))
+ {
+ _registry.SetKSPBuilds(json);
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ catch (Exception e)
+ {
+ _log.WarnFormat("Could not retrieve latest build map from: {0}", BuildMapUri);
+ _log.Debug(e);
+ return false;
+ }
+ }
+
+ private bool TrySetRegistryBuildMap()
+ {
+ try
+ {
+ var json = _registry.GetKSPBuilds();
+ return json != null && TrySetBuildMap(json);
+ }
+ catch(Exception e)
+ {
+ _log.WarnFormat("Could not retrieve build map from registry");
+ _log.Debug(e);
+ return false;
+ }
+ }
+
+ private bool TrySetEmbeddedBuildMap()
+ {
+ try
+ {
+ var resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("CKAN.builds.json");
+
+ if (resourceStream != null)
+ {
+ using (var reader = new StreamReader(resourceStream))
+ {
+ TrySetBuildMap(reader.ReadToEnd());
+ return true;
+ }
+ }
+ else
+ {
+ return false;
+ }
+ }
+ catch(Exception e)
+ {
+ _log.WarnFormat("Could not retrieve build map from embedded resource");
+ _log.Debug(e);
+ return false;
+ }
+ }
+
+ // ReSharper disable once ClassNeverInstantiated.Local
+ private sealed class JBuilds
+ {
+ [JsonProperty("builds")]
+ // ReSharper disable once UnusedAutoPropertyAccessor.Local
+ public Dictionary Builds { get; set; }
+ }
+ }
+}
diff --git a/Core/GameVersionProviders/KspReadmeVersionProvider.cs b/Core/GameVersionProviders/KspReadmeVersionProvider.cs
new file mode 100644
index 0000000000..f1024b8819
--- /dev/null
+++ b/Core/GameVersionProviders/KspReadmeVersionProvider.cs
@@ -0,0 +1,37 @@
+using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+using CKAN.Versioning;
+
+namespace CKAN.GameVersionProviders
+{
+ // ReSharper disable once ClassNeverInstantiated.Global
+ public sealed class KspReadmeVersionProvider : IGameVersionProvider
+ {
+ private static readonly Regex VersionPattern = new Regex(@"^Version\s+(?\d+\.\d+\.\d+)",
+ RegexOptions.IgnoreCase | RegexOptions.Compiled
+ );
+
+ public bool TryGetVersion(string directory, out KspVersion result)
+ {
+ var readmePath = Path.Combine(directory, "readme.txt");
+
+ if (File.Exists(readmePath))
+ {
+ var match = File
+ .ReadAllLines(readmePath)
+ .Select(i => VersionPattern.Match(i))
+ .FirstOrDefault(i => i.Success);
+
+ if (match != null)
+ {
+ result = KspVersion.Parse(match.Groups["version"].Value);
+ return true;
+ }
+ }
+
+ result = default(KspVersion);
+ return false;
+ }
+ }
+}
diff --git a/Core/GameVersionProviders/KspVersionSource.cs b/Core/GameVersionProviders/KspVersionSource.cs
new file mode 100644
index 0000000000..637e21997e
--- /dev/null
+++ b/Core/GameVersionProviders/KspVersionSource.cs
@@ -0,0 +1,8 @@
+namespace CKAN.GameVersionProviders
+{
+ public enum KspVersionSource
+ {
+ BuildId = 1,
+ Readme = 2
+ }
+}
diff --git a/Core/KSP.cs b/Core/KSP.cs
index 7bf2151833..e21e2e01de 100644
--- a/Core/KSP.cs
+++ b/Core/KSP.cs
@@ -6,6 +6,9 @@
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using System.Transactions;
+using Autofac;
+using CKAN.GameVersionProviders;
+using CKAN.Versioning;
using log4net;
[assembly: InternalsVisibleTo("CKAN.Tests")]
@@ -25,7 +28,7 @@ public class KSP : IDisposable
private static readonly ILog log = LogManager.GetLogger(typeof(KSP));
private readonly string gamedir;
- private KSPVersion version;
+ private KspVersion version;
public NetFileCache Cache { get; private set; }
@@ -172,31 +175,7 @@ public static string FindGameDir()
///
internal static bool IsKspDir(string directory)
{
- //first we need to check is directory exists
- if (!Directory.Exists(Path.Combine(directory, "GameData")))
- {
- log.DebugFormat("Cannot find GameData in {0}", directory);
- return false;
- }
-
- if (!File.Exists(Path.Combine(directory, "readme.txt")))
- {
- log.DebugFormat("Cannot find readme in {0}", directory);
- return false;
- }
-
- //If both exist we should be able to get game version
- try
- {
- DetectVersion(directory);
- }
- catch (NotKSPDirKraken)
- {
- log.DebugFormat("Cannot detect KSP version in {0}", directory);
- return false;
- }
- log.DebugFormat("{0} looks like a GameDir", directory);
- return true;
+ return Directory.Exists(Path.Combine(directory, "GameData"));
}
@@ -204,37 +183,39 @@ internal static bool IsKspDir(string directory)
/// Detects the version of KSP in a given directory.
/// Throws a NotKSPDirKraken if anything goes wrong.
///
- private static KSPVersion DetectVersion(string directory)
+ private static KspVersion DetectVersion(string directory)
{
- //Contract.Requires(directory==null);
+ var version = DetectVersionInternal(directory);
- string readme;
- try
+ if (version != null)
{
- // Slurp our README into memory
- readme = File.ReadAllText(Path.Combine(directory, "readme.txt"));
+ log.DebugFormat("Found version {0}", version);
+ return version;
}
- catch
+ else
{
- log.Error("Could not open KSP readme.txt in "+directory);
- throw new NotKSPDirKraken("readme.txt not found or not readable");
+ log.Error("Could not find KSP version");
+ throw new NotKSPDirKraken(directory, "Could not find KSP version in readme.txt");
}
+ }
- // And find the KSP version. Easy! :)
- Match match = Regex.Match(readme, @"^Version\s+(\d+\.\d+\.\d+)",
- RegexOptions.IgnoreCase | RegexOptions.Multiline);
+ private static KspVersion DetectVersionInternal(string directory)
+ {
+ var buildIdVersionProvider = ServiceLocator.Container
+ .ResolveKeyed(KspVersionSource.BuildId);
- if (match.Success)
+ KspVersion version;
+ if (buildIdVersionProvider.TryGetVersion(directory, out version))
{
- string version = match.Groups[1].Value;
- log.DebugFormat("Found version {0}", version);
- return new KSPVersion(version);
+ return version;
}
+ else
+ {
+ var readmeVersionProvider = ServiceLocator.Container
+ .ResolveKeyed(KspVersionSource.Readme);
- // Oh noes! We couldn't find the version!
- log.Error("Could not find KSP version in readme.txt");
-
- throw new NotKSPDirKraken(directory, "Could not find KSP version in readme.txt");
+ return readmeVersionProvider.TryGetVersion(directory, out version) ? version : null;
+ }
}
///
@@ -344,7 +325,7 @@ public string TempDir()
);
}
- public KSPVersion Version()
+ public KspVersion Version()
{
if (version != null)
{
diff --git a/Core/Net/Repo.cs b/Core/Net/Repo.cs
index d525b36a33..872aaac174 100644
--- a/Core/Net/Repo.cs
+++ b/Core/Net/Repo.cs
@@ -4,7 +4,9 @@
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
+using Autofac;
using ChinhDo.Transactions;
+using CKAN.GameVersionProviders;
using ICSharpCode.SharpZipLib.GZip;
using ICSharpCode.SharpZipLib.Tar;
using ICSharpCode.SharpZipLib.Zip;
@@ -139,6 +141,9 @@ public static int Update(RegistryManager registry_manager, KSP ksp, IUser user,
///
internal static void UpdateRegistry(Uri repo, Registry registry, KSP ksp, IUser user, Boolean clear = true)
{
+ // Use this opportunity to also update the build mappings... kind of hacky
+ ServiceLocator.Container.Resolve().Refresh();
+
log.InfoFormat("Downloading {0}", repo);
string repo_file = String.Empty;
diff --git a/Core/Registry/AvailableModule.cs b/Core/Registry/AvailableModule.cs
index dfa1b316e5..03b8edf783 100644
--- a/Core/Registry/AvailableModule.cs
+++ b/Core/Registry/AvailableModule.cs
@@ -3,6 +3,7 @@
using System.Diagnostics;
using System.Linq;
using System.Runtime.Serialization;
+using CKAN.Versioning;
using log4net;
using Newtonsoft.Json;
@@ -71,7 +72,7 @@ public void Remove(Version version)
/// If not null only consider mods which match this ksp version.
/// If not null only consider mods which satisfy the RelationshipDescriptor.
///
- public CkanModule Latest(KSPVersion ksp_version = null, RelationshipDescriptor relationship=null)
+ public CkanModule Latest(KspVersion ksp_version = null, RelationshipDescriptor relationship=null)
{
var available_versions = new List(module_version.Keys);
CkanModule module;
diff --git a/Core/Registry/IRegistryQuerier.cs b/Core/Registry/IRegistryQuerier.cs
index be3e7e5b6a..36bc855a96 100644
--- a/Core/Registry/IRegistryQuerier.cs
+++ b/Core/Registry/IRegistryQuerier.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using CKAN.Versioning;
namespace CKAN
{
@@ -15,7 +16,7 @@ public interface IRegistryQuerier
/// the specified version of KSP.
///
// TODO: This name is misleading. It's more a LatestAvailable's'
- List Available(KSPVersion ksp_version);
+ List Available(KspVersion ksp_version);
///
/// Returns the latest available version of a module that
@@ -24,7 +25,7 @@ public interface IRegistryQuerier
/// If no ksp_version is provided, the latest module for *any* KSP is returned.
/// Throws if asked for a non-existent module.
///
- CkanModule LatestAvailable(string identifier, KSPVersion ksp_version, RelationshipDescriptor relationship_descriptor = null);
+ CkanModule LatestAvailable(string identifier, KspVersion ksp_version, RelationshipDescriptor relationship_descriptor = null);
///
/// Returns the latest available version of a module that satisifes the specified version and
@@ -33,7 +34,7 @@ public interface IRegistryQuerier
/// Returns an empty list if nothing is available for our system, which includes if no such module exists.
/// If no KSP version is provided, the latest module for *any* KSP version is given.
///
- List LatestAvailableWithProvides(string identifier, KSPVersion ksp_version, RelationshipDescriptor relationship_descriptor = null);
+ List LatestAvailableWithProvides(string identifier, KspVersion ksp_version, RelationshipDescriptor relationship_descriptor = null);
///
/// Checks the sanity of the registry, to ensure that all dependencies are met,
@@ -64,7 +65,7 @@ public interface IRegistryQuerier
/// Returns a simple array of all incompatible modules for
/// the specified version of KSP.
///
- List Incompatible(KSPVersion ksp_version);
+ List Incompatible(KspVersion ksp_version);
///
/// Returns a dictionary of all modules installed, along with their
@@ -127,7 +128,7 @@ public static bool IsAutodetected(this IRegistryQuerier querier, string identifi
/// Is the mod installed and does it have a newer version compatible with version
/// We can't update AD mods
///
- public static bool HasUpdate(this IRegistryQuerier querier, string identifier, KSPVersion version)
+ public static bool HasUpdate(this IRegistryQuerier querier, string identifier, KspVersion version)
{
CkanModule newest_version;
try
diff --git a/Core/Registry/Registry.cs b/Core/Registry/Registry.cs
index 189e4377e4..34abeed78d 100644
--- a/Core/Registry/Registry.cs
+++ b/Core/Registry/Registry.cs
@@ -5,6 +5,7 @@
using System.Runtime.Serialization;
using System.Text.RegularExpressions;
using System.Transactions;
+using CKAN.Versioning;
using log4net;
using Newtonsoft.Json;
@@ -402,7 +403,7 @@ public void RemoveAvailable(CkanModule module)
///
///
///
- public List Available(KSPVersion ksp_version)
+ public List Available(KspVersion ksp_version)
{
var candidates = new List(available_modules.Keys);
var compatible = new List();
@@ -460,7 +461,7 @@ public List Available(KSPVersion ksp_version)
///
///
///
- public List Incompatible(KSPVersion ksp_version)
+ public List Incompatible(KspVersion ksp_version)
{
var candidates = new List(available_modules.Keys);
var incompatible = new List();
@@ -491,7 +492,7 @@ public List Incompatible(KSPVersion ksp_version)
// be calling LatestAvailableWithProvides()
public CkanModule LatestAvailable(
string module,
- KSPVersion ksp_version,
+ KspVersion ksp_version,
RelationshipDescriptor relationship_descriptor =null)
{
log.DebugFormat("Finding latest available for {0}", module);
@@ -513,7 +514,7 @@ public CkanModule LatestAvailable(
///
///
///
- public List LatestAvailableWithProvides(string module, KSPVersion ksp_version, RelationshipDescriptor relationship_descriptor = null)
+ public List LatestAvailableWithProvides(string module, KspVersion ksp_version, RelationshipDescriptor relationship_descriptor = null)
{
// This public interface calculates a cache of modules which
// are compatible with the current version of KSP, and then
@@ -529,7 +530,7 @@ public List LatestAvailableWithProvides(string module, KSPVersion ks
/// the `available_for_current_version` list has been correctly
/// calculated. Not for direct public consumption. ;)
///
- private List LatestAvailableWithProvides(string module, KSPVersion ksp_version,
+ private List LatestAvailableWithProvides(string module, KspVersion ksp_version,
IEnumerable available_for_current_version, RelationshipDescriptor relationship_descriptor=null)
{
log.DebugFormat("Finding latest available with provides for {0}", module);
diff --git a/Core/Relationships/RelationshipResolver.cs b/Core/Relationships/RelationshipResolver.cs
index 67db1da237..765d969f8c 100644
--- a/Core/Relationships/RelationshipResolver.cs
+++ b/Core/Relationships/RelationshipResolver.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
+using CKAN.Versioning;
using log4net;
namespace CKAN
@@ -88,7 +89,7 @@ public class RelationshipResolver
new Dictionary(new NameComparer());
private readonly IRegistryQuerier registry;
- private readonly KSPVersion kspversion;
+ private readonly KspVersion kspversion;
private readonly RelationshipResolverOptions options;
private readonly HashSet installed_modules;
@@ -98,7 +99,7 @@ public class RelationshipResolver
///
/// The registry to use
/// The version of the install that the registry corresponds to
- public RelationshipResolver(RelationshipResolverOptions options, IRegistryQuerier registry, KSPVersion kspversion)
+ public RelationshipResolver(RelationshipResolverOptions options, IRegistryQuerier registry, KspVersion kspversion)
{
this.registry = registry;
this.kspversion = kspversion;
@@ -120,7 +121,7 @@ public RelationshipResolver(RelationshipResolverOptions options, IRegistryQuerie
///
///
public RelationshipResolver(IEnumerable module_names, RelationshipResolverOptions options, IRegistryQuerier registry,
- KSPVersion kspversion) :
+ KspVersion kspversion) :
this(module_names.Select(name => CkanModule.FromIDandVersion(registry, name, kspversion)).ToList(),
options,
registry,
@@ -133,7 +134,7 @@ public RelationshipResolver(IEnumerable module_names, RelationshipResolv
/// Creates a new resolver that will find a way to install all the modules specified.
///
public RelationshipResolver(IEnumerable modules, RelationshipResolverOptions options, IRegistryQuerier registry,
- KSPVersion kspversion):this(options,registry,kspversion)
+ KspVersion kspversion):this(options,registry,kspversion)
{
AddModulesToInstall(modules);
}
diff --git a/Core/ServiceLocator.cs b/Core/ServiceLocator.cs
index 31a259b05a..ab44d1dc6e 100644
--- a/Core/ServiceLocator.cs
+++ b/Core/ServiceLocator.cs
@@ -1,4 +1,5 @@
using Autofac;
+using CKAN.GameVersionProviders;
namespace CKAN
{
@@ -32,7 +33,23 @@ private static void Init()
{
var builder = new ContainerBuilder();
- builder.RegisterType().As();
+ builder.RegisterType()
+ .As();
+
+ builder.RegisterType()
+ .As();
+
+ builder.RegisterType()
+ .As()
+ .SingleInstance(); // Since it stores cached data we want to keep it around
+
+ builder.RegisterType()
+ .As()
+ .Keyed(KspVersionSource.BuildId);
+
+ builder.RegisterType()
+ .As()
+ .Keyed(KspVersionSource.Readme);
_container = builder.Build();
}
diff --git a/Core/Types/CkanModule.cs b/Core/Types/CkanModule.cs
index 16278ec067..d07283aa49 100644
--- a/Core/Types/CkanModule.cs
+++ b/Core/Types/CkanModule.cs
@@ -8,6 +8,7 @@
using Newtonsoft.Json;
using System.Transactions;
using Autofac;
+using CKAN.Versioning;
namespace CKAN
{
@@ -159,13 +160,13 @@ public class CkanModule : IEquatable
public string identifier;
[JsonProperty("ksp_version")]
- public KSPVersion ksp_version;
+ public KspVersion ksp_version;
[JsonProperty("ksp_version_max")]
- public KSPVersion ksp_version_max;
+ public KspVersion ksp_version_max;
[JsonProperty("ksp_version_min")]
- public KSPVersion ksp_version_min;
+ public KspVersion ksp_version_min;
[JsonProperty("ksp_version_strict")]
public bool ksp_version_strict = false;
@@ -339,31 +340,8 @@ private void DeSerialisationFixes(StreamingContext like_i_could_care)
// Make sure our version fields are populated.
// TODO: There's got to be a better way of doing this, right?
- if (ksp_version_min == null)
- {
- ksp_version_min = new KSPVersion(null);
- }
- else
- {
- ksp_version_min = ksp_version_min.ToLongMin();
- }
-
- if (ksp_version_max == null)
- {
- ksp_version_max = new KSPVersion(null);
- }
- else
- {
- ksp_version_max = ksp_version_max.ToLongMax();
- }
-
- if (ksp_version == null)
- {
- ksp_version = new KSPVersion(null);
- }
-
// Now see if we've got version with version min/max.
- if (ksp_version.IsNotAny() && (ksp_version_max.IsNotAny() || ksp_version_min.IsNotAny()))
+ if (ksp_version != null && (ksp_version_max != null || ksp_version_min != null))
{
// KSP version mixed with min/max.
throw new InvalidModuleAttributesException("ksp_version mixed with ksp_version_(min|max)", this);
@@ -423,7 +401,7 @@ private static bool validate_json_against_schema(string json)
/// Tries to parse an identifier in the format Modname=version
/// If the module cannot be found in the registry, throws a ModuleNotFoundKraken.
///
- public static CkanModule FromIDandVersion(IRegistryQuerier registry, string mod, KSPVersion ksp_version)
+ public static CkanModule FromIDandVersion(IRegistryQuerier registry, string mod, KspVersion ksp_version)
{
CkanModule module;
@@ -512,16 +490,17 @@ internal static bool UniConflicts(CkanModule mod1, CkanModule mod2)
///
public bool IsCompatibleKSP(string version)
{
- return IsCompatibleKSP(new KSPVersion(version));
+ return IsCompatibleKSP(KspVersion.Parse(version));
}
///
/// Returns true if our mod is compatible with the KSP version specified.
///
- public bool IsCompatibleKSP(KSPVersion version)
+ public bool IsCompatibleKSP(KspVersion version)
{
log.DebugFormat("Testing if {0} is compatible with KSP {1}", this, version);
+
return _comparator.Compatible(version, this);
}
@@ -536,17 +515,17 @@ public bool IsCompatibleKSP(KSPVersion version)
public string HighestCompatibleKSP()
{
// Find the highest compatible KSP version
- if (!String.IsNullOrEmpty(ksp_version_max.ToString()))
+ if (ksp_version_max != null)
{
- return ksp_version_max.ToLongMax().ToString();
+ return ksp_version_max.ToString();
}
- else if (!String.IsNullOrEmpty(ksp_version.ToString()))
+ else if (ksp_version != null)
{
- return ksp_version.ToLongMax().ToString();
+ return ksp_version.ToString();
}
- else if (!String.IsNullOrEmpty(ksp_version_min.ToString()))
+ else if (ksp_version_min != null )
{
- return ksp_version_min.ToLongMin().ToString() + "+";
+ return ksp_version_min + "+";
}
return "All versions";
diff --git a/Core/Types/GameComparator/GrasGameComparator.cs b/Core/Types/GameComparator/GrasGameComparator.cs
index 7ca2f4437b..bfb2518655 100644
--- a/Core/Types/GameComparator/GrasGameComparator.cs
+++ b/Core/Types/GameComparator/GrasGameComparator.cs
@@ -1,4 +1,4 @@
-using System;
+using CKAN.Versioning;
namespace CKAN
{
@@ -10,9 +10,9 @@ namespace CKAN
public class GrasGameComparator : IGameComparator
{
static readonly StrictGameComparator strict = new StrictGameComparator();
- static readonly KSPVersion v103 = new KSPVersion("1.0.3");
+ static readonly KspVersion v103 = KspVersion.Parse("1.0.3");
- public bool Compatible(KSPVersion gameVersion, CkanModule module)
+ public bool Compatible(KspVersion gameVersion, CkanModule module)
{
// If it's strictly compatible, then it's compatible.
if (strict.Compatible(gameVersion, module))
@@ -27,11 +27,10 @@ public bool Compatible(KSPVersion gameVersion, CkanModule module)
// If we're running KSP 1.0.4, then allow the mod to run if we would have
// considered it compatible under 1.0.3 (as 1.0.4 was "just a hotfix").
- if (gameVersion.Equals("1.0.4"))
+ if (gameVersion.Equals(KspVersion.Parse("1.0.4")))
return strict.Compatible(v103, module);
return false;
}
}
}
-
diff --git a/Core/Types/GameComparator/IGameComparator.cs b/Core/Types/GameComparator/IGameComparator.cs
index e0559ba1e7..79e660b71f 100644
--- a/Core/Types/GameComparator/IGameComparator.cs
+++ b/Core/Types/GameComparator/IGameComparator.cs
@@ -1,4 +1,4 @@
-using System;
+using CKAN.Versioning;
namespace CKAN
{
@@ -11,7 +11,7 @@ public interface IGameComparator
/// Returns true if the given module is compatible with the supplied
/// gameVersion, false otherwise.
///
- bool Compatible(KSPVersion gameVersion, CkanModule module);
+ bool Compatible(KspVersion gameVersion, CkanModule module);
}
}
diff --git a/Core/Types/GameComparator/StrictGameComparator.cs b/Core/Types/GameComparator/StrictGameComparator.cs
index dace6381c0..0049d8195f 100644
--- a/Core/Types/GameComparator/StrictGameComparator.cs
+++ b/Core/Types/GameComparator/StrictGameComparator.cs
@@ -1,4 +1,4 @@
-using System;
+using CKAN.Versioning;
namespace CKAN
{
@@ -8,33 +8,51 @@ namespace CKAN
///
public class StrictGameComparator : IGameComparator
{
-
- public bool Compatible(KSPVersion gameVersion, CkanModule module)
+ public bool Compatible(KspVersion gameVersion, CkanModule module)
{
- KSPVersion ksp_version = module.ksp_version;
- KSPVersion ksp_version_min = module.ksp_version_min;
- KSPVersion ksp_version_max = module.ksp_version_max;
+ var gameVersionRange = gameVersion.ToVersionRange();
- // Check the min and max versions.
+ var moduleRange = KspVersionRange.Any;
- if (ksp_version_min.IsNotAny() && gameVersion < ksp_version_min)
+ if (module.ksp_version != null)
{
- return false;
+ moduleRange = module.ksp_version.ToVersionRange();
}
-
- if (ksp_version_max.IsNotAny() && gameVersion > ksp_version_max)
+ else if (module.ksp_version_min != null || module.ksp_version_max != null)
{
- return false;
+ if (module.ksp_version_min != null && module.ksp_version_max != null)
+ {
+ if (module.ksp_version_min <= module.ksp_version_max)
+ {
+ var minRange = module.ksp_version_min.ToVersionRange();
+ var maxRange = module.ksp_version_max.ToVersionRange();
+
+ moduleRange = new KspVersionRange(minRange.Lower, maxRange.Upper);
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else if (module.ksp_version_min != null)
+ {
+ var minRange = module.ksp_version_min.ToVersionRange();
+
+ moduleRange = new KspVersionRange(minRange.Lower, KspVersionBound.Unbounded);
+ }
+ else if (module.ksp_version_max != null)
+ {
+ var maxRange = module.ksp_version_max.ToVersionRange();
+
+ moduleRange = new KspVersionRange(KspVersionBound.Unbounded, maxRange.Upper);
+ }
+ }
+ else
+ {
+ return true;
}
- // We didn't hit the min/max guards. They may not have existed.
-
- // Note that since ksp_version is "any" if not specified, this
- // will work fine if there's no target, or if there were min/max
- // fields and we passed them successfully.
-
- return ksp_version.Targets(gameVersion);
+ return moduleRange.IsSupersetOf(gameVersionRange);
}
}
}
-
diff --git a/Core/Types/GameComparator/YoyoGameComparator.cs b/Core/Types/GameComparator/YoyoGameComparator.cs
index c0a47b39f0..8824cd43fb 100644
--- a/Core/Types/GameComparator/YoyoGameComparator.cs
+++ b/Core/Types/GameComparator/YoyoGameComparator.cs
@@ -1,4 +1,4 @@
-using System;
+using CKAN.Versioning;
namespace CKAN
{
@@ -8,7 +8,7 @@ namespace CKAN
///
public class YoyoGameComparator : IGameComparator
{
- public bool Compatible(KSPVersion gameVersion, CkanModule module)
+ public bool Compatible(KspVersion gameVersion, CkanModule module)
{
return true;
}
diff --git a/Core/Types/KSPVersion.cs b/Core/Types/KSPVersion.cs
deleted file mode 100644
index 7d2d5f5679..0000000000
--- a/Core/Types/KSPVersion.cs
+++ /dev/null
@@ -1,310 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text.RegularExpressions;
-using Newtonsoft.Json;
-
-namespace CKAN
-{
- [JsonConverter(typeof (JsonSimpleStringConverter))]
- public class KSPVersion : IComparable
- {
- private readonly string version;
- private Version cached_version_object;
- private readonly bool is_short;
-
- private static readonly Dictionary> NormalCache =
- new Dictionary>();
-
- public KSPVersion(string v)
- {
- Tuple normalized;
-
- if (v != null && NormalCache.TryGetValue(v, out normalized))
- {
- version = normalized.Item1;
- is_short = version != null && normalized.Item2;
- }
- else
- {
- version = Normalise(v);
- version = AnyToNull(version);
- is_short = version != null && Regex.IsMatch(version, @"^\d+\.\d+$");
- Validate(); // Throws on error.
- if (v != null)
- {
- NormalCache.Add(v, new Tuple(version, is_short));
- }
- }
- }
-
- // Casting function
- public static explicit operator KSPVersion(string v)
- {
- return new KSPVersion(v);
- }
-
- ///
- /// Normalises a version number. Currently this adds
- /// a leading zero if it's missing one.
- ///
- /// V.
- private static string Normalise(string v)
- {
- if (v == null)
- {
- return null;
- }
-
- if (Regex.IsMatch(v, @"^\."))
- {
- return "0" + v;
- }
-
- return v;
- }
-
- ///
- /// If our KSP version is already long (x.y.z) returns itself. If our
- /// KSP version is short (x.y) returns the minimum full release that
- /// would match (x.y.0).
- ///
- /// It is a mistake to call this method without using its return value.
- ///
- public KSPVersion ToLongMin()
- {
- return IsShortVersion() ? new KSPVersion(version + ".0") : this;
- }
-
- ///
- /// If our KSP version is already long (x.y.z) returns itself. If our
- /// KSP version is short (x.y) returns the maximum full release that
- /// would match (x.y.99).
- ///
- /// It is a mistake to call this method without using its return value.
- ///
- public KSPVersion ToLongMax()
- {
- return IsShortVersion() ? new KSPVersion(version + ".99") : this;
- }
-
- ///
- /// Returns true if our version object is short (ie: missing a patch number,
- /// such as `0.23`).
- ///
- public bool IsShortVersion()
- {
- return is_short;
- }
-
- ///
- /// Returns true if our version is long (ie: includes a patch number, such
- /// as `0.23.5`).
- ///
- public bool IsLongVersion()
- {
- return !is_short;
- }
-
- public bool IsAny()
- {
- return version == null;
- }
-
- public bool IsNotAny()
- {
- return !IsAny();
- }
-
- public string Version()
- {
- return version;
- }
-
- // Private for now, since we can't guarnatee public code will only call
- // us with long versions.
- private Version VersionObject()
- {
- return cached_version_object ?? (cached_version_object = new Version(version));
- }
-
- private static readonly Dictionary, int> CompareCache
- = new Dictionary, int>();
-
- public int CompareTo(KSPVersion that)
- {
- int ret;
- var tuple = new Tuple(this, that);
- if (CompareCache.TryGetValue(tuple, out ret))
- return ret;
-
-
- // We need two long versions to be able to compare properly.
- if ((!IsLongVersion()) && (!that.IsLongVersion()))
- {
- throw new KSPVersionIncomparableException(this, that, "CompareTo");
- }
-
- // Hooray, we can hook the regular Version code here.
-
- Version v1 = VersionObject();
- Version v2 = that.VersionObject();
- ret = v1.CompareTo(v2);
- CompareCache.Add(tuple, ret);
- return ret;
- }
-
- public bool Targets(string target_version)
- {
- return Targets(new KSPVersion(target_version));
- }
-
- ///
- /// Returns true if this targets that version of KSP.
- /// That must be a long (actual) version.
- /// Eg: 0.25 targets 0.25.2
- ///
- public bool Targets(KSPVersion that)
- {
- // Cry if we're not looking at a long version to compare to.
- if (!that.IsLongVersion())
- {
- throw new KSPVersionIncomparableException(this, that, "Targets");
- }
-
- // If we target any, then yes, it's a match.
- if (IsAny())
- {
- return true;
- }
- else if (IsLongVersion())
- {
- return CompareTo(that) == 0;
- }
-
- // We've got a short version, so split it into two separate versions,
- // and compare each.
-
- KSPVersion min = ToLongMin();
-
- KSPVersion max = ToLongMax();
-
- return (that >= min && that <= max);
- }
-
- ///
- /// Returns null if passed null, or the string "any",
- /// otherwise returns the string passed.
- ///
- private static string AnyToNull(string v)
- {
- if (v != null && v == "any")
- {
- return null;
- }
- return v;
- }
-
- ///
- /// Throws an exception if our version object is not well-formed.
- ///
- private void Validate()
- {
- if (version == null || IsShortVersion() || IsLongVersion())
- {
- return;
- }
- throw new BadKSPVersionException(version);
- }
-
- // Why don't I get operator overloads for free?
- // Is there a class I can delcare allegiance to that gives me this?
- // Where's my ComparableOperators role?
-
- public static bool operator <(KSPVersion v1, KSPVersion v2)
- {
- return v1.CompareTo(v2) < 0;
- }
-
- public static bool operator <=(KSPVersion v1, KSPVersion v2)
- {
- return v1.CompareTo(v2) <= 0;
- }
-
- public static bool operator >(KSPVersion v1, KSPVersion v2)
- {
- return v1.CompareTo(v2) > 0;
- }
-
- public static bool operator >=(KSPVersion v1, KSPVersion v2)
- {
- return v1.CompareTo(v2) >= 0;
- }
-
- public override string ToString()
- {
- return Version();
- }
-
- protected bool Equals(KSPVersion other)
- {
- return string.Equals(version, other.version);
- }
-
- ///
- /// Compare the version against a string. This only makes sense for complete
- /// versions (eg: '1.0.4').
- ///
- public bool Equals(string version_string)
- {
- return string.Equals(version, version_string);
- }
-
- public override bool Equals(object obj)
- {
- if (ReferenceEquals(null, obj)) return false;
- if (ReferenceEquals(this, obj)) return true;
- if (obj.GetType() != this.GetType()) return false;
- return Equals((KSPVersion) obj);
- }
-
- public override int GetHashCode()
- {
- return (version != null ? version.GetHashCode() : 0);
- }
- }
-
- public class BadKSPVersionException : Exception
- {
- private readonly string version;
-
- public BadKSPVersionException(string v)
- {
- version = v;
- }
-
- public override string ToString()
- {
- return string.Format("[BadKSPVersionException] {0} is not a valid KSP version", version);
- }
- }
-
- public class KSPVersionIncomparableException : Exception
- {
- private readonly string version1;
- private readonly string version2;
- private readonly string method;
-
- public KSPVersionIncomparableException(KSPVersion v1, KSPVersion v2, string m)
- {
- version1 = v1.Version();
- version2 = v2.Version();
- method = m;
- }
-
- public override string ToString()
- {
- return string.Format("[KSPVersionIncomparableException] {0} and {1} cannot be compared by {2}", version1,
- version2, method);
- }
- }
-}
diff --git a/Core/Versioning/KspVersion.cs b/Core/Versioning/KspVersion.cs
new file mode 100644
index 0000000000..eb8ed9c87a
--- /dev/null
+++ b/Core/Versioning/KspVersion.cs
@@ -0,0 +1,710 @@
+using System;
+using System.Text;
+using System.Text.RegularExpressions;
+using Newtonsoft.Json;
+
+namespace CKAN.Versioning
+{
+ ///
+ /// Represents the version number of a Kerbal Space Program (KSP) installation.
+ ///
+ [JsonConverter(typeof(KspVersionJsonConverter))]
+ public sealed partial class KspVersion
+ {
+ private static readonly Regex Pattern = new Regex(
+ @"^(?\d+)(?:\.(?\d+)(?:\.(?\d+)(?:\.(?\d+))?)?)?$",
+ RegexOptions.Compiled
+ );
+
+ private const int Undefined = -1;
+
+ public static readonly KspVersion Any = new KspVersion();
+
+ private readonly int _major;
+ private readonly int _minor;
+ private readonly int _patch;
+ private readonly int _build;
+
+ private readonly string _string;
+
+ ///
+ /// Gets the value of the major component of the version number for the current
+ /// object.
+ ///
+ public int Major { get { return _major; } }
+
+ ///
+ /// Gets the value of the minor component of the version number for the current
+ /// object.
+ ///
+ public int Minor { get { return _minor; } }
+
+ ///
+ /// Gets the value of the patch component of the version number for the current
+ /// object.
+ ///
+ public int Patch { get { return _patch; } }
+
+ ///
+ /// Gets the value of the build component of the version number for the current
+ /// object.
+ ///
+ public int Build { get { return _build; } }
+
+ ///
+ /// Gets whether or not the major component of the version number for the current
+ /// object is defined.
+ ///
+ public bool IsMajorDefined { get { return _major != Undefined; } }
+
+ ///
+ /// Gets whether or not the minor component of the version number for the current
+ /// object is defined.
+ ///
+ public bool IsMinorDefined { get { return _minor != Undefined; } }
+
+ ///
+ /// Gets whether or not the patch component of the version number for the current
+ /// object is defined.
+ ///
+ public bool IsPatchDefined { get { return _patch != Undefined; } }
+
+ ///
+ /// Gets whether or not the build component of the version number for the current
+ /// object is defined.
+ ///
+ public bool IsBuildDefined { get { return _build != Undefined; } }
+
+ ///
+ /// Indicates whether or not all components of the current are defined.
+ ///
+ public bool IsFullyDefined
+ {
+ get { return IsMajorDefined && IsMinorDefined && IsPatchDefined && IsBuildDefined; }
+ }
+
+ ///
+ /// Indicates wheter or not all the components of the current are undefined.
+ ///
+ public bool IsAny
+ {
+ get { return !IsMajorDefined && !IsMinorDefined && !IsPatchDefined && !IsBuildDefined; }
+ }
+
+ ///
+ /// Initialize a new instance of the class with all components unspecified.
+ ///
+ public KspVersion()
+ {
+ _major = Undefined;
+ _minor = Undefined;
+ _patch = Undefined;
+ _build = Undefined;
+
+ _string = DeriveString(_major, _minor, _patch, _build);
+ }
+
+ ///
+ /// Initialize a new instance of the class using the specified major value.
+ ///
+ /// The major version number.
+ public KspVersion(int major)
+ {
+ if (major < 0)
+ throw new ArgumentOutOfRangeException("major");
+
+ _major = major;
+ _minor = Undefined;
+ _patch = Undefined;
+ _build = Undefined;
+
+ _string = DeriveString(_major, _minor, _patch, _build);
+ }
+
+ ///
+ /// Initialize a new instance of the class using the specified major and minor
+ /// values.
+ ///
+ /// The major version number.
+ /// The minor version number.
+ public KspVersion(int major, int minor)
+ {
+ if (major < 0)
+ throw new ArgumentOutOfRangeException("major");
+
+ if (minor < 0)
+ throw new ArgumentOutOfRangeException("minor");
+
+ _major = major;
+ _minor = minor;
+ _patch = Undefined;
+ _build = Undefined;
+
+ _string = DeriveString(_major, _minor, _patch, _build);
+ }
+
+ ///
+ /// Initialize a new instance of the class using the specified major, minor, and
+ /// patch values.
+ ///
+ /// The major version number.
+ /// The minor version number.
+ /// The patch version number.
+ public KspVersion(int major, int minor, int patch)
+ {
+ if (major < 0)
+ throw new ArgumentOutOfRangeException("major");
+
+ if (minor < 0)
+ throw new ArgumentOutOfRangeException("minor");
+
+ if (patch < 0)
+ throw new ArgumentOutOfRangeException("patch");
+
+ _major = major;
+ _minor = minor;
+ _patch = patch;
+ _build = Undefined;
+
+ _string = DeriveString(_major, _minor, _patch, _build);
+ }
+
+ ///
+ /// Initialize a new instance of the class using the specified major, minor, patch,
+ /// and build values.
+ ///
+ /// The major version number.
+ /// The minor version number.
+ /// The patch version number.
+ /// The build verison number.
+ public KspVersion(int major, int minor, int patch, int build)
+ {
+ if (major < 0)
+ throw new ArgumentOutOfRangeException("major");
+
+ if (minor < 0)
+ throw new ArgumentOutOfRangeException("minor");
+
+ if (patch < 0)
+ throw new ArgumentOutOfRangeException("patch");
+
+ if (build < 0)
+ throw new ArgumentOutOfRangeException("build");
+
+ _major = major;
+ _minor = minor;
+ _patch = patch;
+ _build = build;
+
+ _string = DeriveString(_major, _minor, _patch, _build);
+ }
+
+ ///
+ /// Converts the value of the current to its equivalent
+ /// representation.
+ ///
+ ///
+ ///
+ /// The representation of the values of the major, minor, patch, and build components of
+ /// the current object as depicted in the following format. Each component is
+ /// separated by a period character ('.'). Square brackets ('[' and ']') indicate a component that will not
+ /// appear in the return value if the component is not defined:
+ ///
+ ///
+ /// [major[.minor[.patch[.build]]]]
+ ///
+ ///
+ /// For example, if you create a object using the constructor KspVersion(1,1),
+ /// the returned string is "1.1". If you create a using the constructor (1,3,4,2),
+ /// the returned string is "1.3.4.2".
+ ///
+ ///
+ /// If the current is totally undefined the return value will null.
+ ///
+ ///
+ public override string ToString()
+ {
+ return _string;
+ }
+
+ ///
+ /// Converts the value of the current to its equivalent
+ /// .
+ ///
+ ///
+ ///
+ /// A which specifies a set of versions equivalent to the current
+ /// .
+ ///
+ ///
+ /// For example, the version "1.0.0.0" would be equivalent to the range ["1.0.0.0", "1.0.0.0"], while the
+ /// version "1.0" would be equivalent to the range ["1.0.0.0", "1.1.0.0"). Where '[' and ']' represent
+ /// inclusive bounds and '(' and ')' represent exclusive bounds.
+ ///
+ ///
+ public KspVersionRange ToVersionRange()
+ {
+ KspVersionBound lower;
+ KspVersionBound upper;
+
+ if (IsBuildDefined)
+ {
+ lower = new KspVersionBound(this, inclusive: true);
+ upper = new KspVersionBound(this, inclusive: true);
+ }
+ else if (IsPatchDefined)
+ {
+ lower = new KspVersionBound(new KspVersion(Major, Minor, Patch, 0), inclusive: true);
+ upper = new KspVersionBound(new KspVersion(Major, Minor, Patch + 1, 0), inclusive: false);
+ }
+ else if (IsMinorDefined)
+ {
+ lower = new KspVersionBound(new KspVersion(Major, Minor, 0, 0), inclusive: true);
+ upper = new KspVersionBound(new KspVersion(Major, Minor + 1, 0, 0), inclusive: false);
+ }
+ else if (IsMajorDefined)
+ {
+ lower = new KspVersionBound(new KspVersion(Major, 0, 0, 0), inclusive: true);
+ upper = new KspVersionBound(new KspVersion(Major + 1, 0, 0, 0), inclusive: false);
+ }
+ else
+ {
+ lower = KspVersionBound.Unbounded;
+ upper = KspVersionBound.Unbounded;
+ }
+
+ return new KspVersionRange(lower, upper);
+ }
+
+ ///
+ /// Converts the string representation of a version number to an equivalent object.
+ ///
+ /// A string that contains a version number to convert.
+ ///
+ /// A object that is equivalent to the version number specified in the
+ /// parameter.
+ ///
+ public static KspVersion Parse(string input)
+ {
+ if (ReferenceEquals(input, null))
+ throw new ArgumentNullException("input");
+
+ KspVersion result;
+ if (TryParse(input, out result))
+ return result;
+ else
+ throw new FormatException();
+ }
+
+ ///
+ /// Tries to convert the string representation of a version number to an equivalent
+ /// object and returns a value that indicates whether the conversion succeeded.
+ ///
+ ///
+ /// A string that contains a version number to convert.
+ ///
+ ///
+ /// When this method returns true, contains the equivalent of the number that
+ /// is contained in . When this method returns false, the value is unspecified.
+ ///
+ ///
+ /// true if the parameter was converted successfully; otherwise, false.
+ ///
+ public static bool TryParse(string input, out KspVersion result)
+ {
+ result = null;
+
+ if (ReferenceEquals(input, null))
+ return false;
+
+ var major = Undefined;
+ var minor = Undefined;
+ var patch = Undefined;
+ var build = Undefined;
+
+ var match = Pattern.Match(input.Trim());
+
+ if (match.Success)
+ {
+ var majorGroup = match.Groups["major"];
+ var minorGroup = match.Groups["minor"];
+ var patchGroup = match.Groups["patch"];
+ var buildGroup = match.Groups["build"];
+
+ if (majorGroup.Success)
+ if (!int.TryParse(majorGroup.Value, out major))
+ return false;
+
+ if (minorGroup.Success)
+ if (!int.TryParse(minorGroup.Value, out minor))
+ return false;
+
+ if (patchGroup.Success)
+ if (!int.TryParse(patchGroup.Value, out patch))
+ return false;
+
+ if (buildGroup.Success)
+ if (!int.TryParse(buildGroup.Value, out build))
+ return false;
+
+ if (minor == Undefined)
+ result = new KspVersion(major);
+ else if (patch == Undefined)
+ result = new KspVersion(major, minor);
+ else if (build == Undefined)
+ result = new KspVersion(major, minor, patch);
+ else
+ result = new KspVersion(major, minor, patch, build);
+
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+
+ public sealed partial class KspVersion : IEquatable
+ {
+ ///
+ /// Returns a value indicating whether the current object and specified
+ /// object represent the same value.
+ ///
+ ///
+ /// A object to compare to the current object, or
+ /// null.
+ ///
+ ///
+ /// true if every component of the current matches the corresponding component
+ /// of the parameter; otherwise, false.
+ ///
+ public bool Equals(KspVersion obj)
+ {
+ if (ReferenceEquals(obj, null)) return false;
+ if (ReferenceEquals(obj, this)) return true;
+ return _major == obj._major && _minor == obj._minor && _patch == obj._patch && _build == obj._build;
+ }
+
+ ///
+ /// Returns a value indicating whether the current object is equal to a specified
+ /// object.
+ ///
+ ///
+ /// An object to compare with the current object, or null.
+ ///
+ ///
+ /// true if the current object and are both
+ /// objects and every component of the current object
+ /// matches the corresponding component of ; otherwise, false.
+ ///
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(obj, null)) return false;
+ if (ReferenceEquals(obj, this)) return true;
+ return obj is KspVersion && Equals((KspVersion) obj);
+ }
+
+ ///
+ /// Returns a hash code for the current object.
+ ///
+ /// A 32-bit signed integer hash code.
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ var hashCode = _major.GetHashCode();
+ hashCode = (hashCode*397) ^ _minor.GetHashCode();
+ hashCode = (hashCode*397) ^ _patch.GetHashCode();
+ hashCode = (hashCode*397) ^ _build.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ ///
+ /// Determines whether two specified objects are equal.
+ ///
+ /// The first object.
+ /// The second object.
+ /// true if equals ; otherwise, false.
+ public static bool operator ==(KspVersion v1, KspVersion v2)
+ {
+ return Equals(v1, v2);
+ }
+
+ ///
+ /// Determines whether two specified objects are not equal.
+ ///
+ /// The first object.
+ /// The second object.
+ ///
+ /// true if does not equal ; otherwise, false.
+ ///
+ public static bool operator !=(KspVersion v1, KspVersion v2)
+ {
+ return !Equals(v1, v2);
+ }
+ }
+
+ public sealed partial class KspVersion : IComparable, IComparable
+ {
+ ///
+ /// Compares the current object to a specified object and returns an indication of
+ /// their relative values.
+ ///
+ /// An object to compare, or null.
+ ///
+ /// A signed integer that indicates the relative values of the two objects, as shown in the following table.
+ ///
+ ///
+ /// Return value
+ /// Meaning
+ ///
+ /// -
+ /// Less than zero
+ ///
+ /// The current object is a version before .
+ ///
+ ///
+ /// -
+ /// Zero
+ ///
+ /// The current object is the same version as .
+ ///
+ ///
+ /// -
+ /// Greater than zero
+ ///
+ ///
+ /// The current object is a version subsequent to .
+ ///
+ ///
+ ///
+ ///
+ ///
+ public int CompareTo(object obj)
+ {
+ if (ReferenceEquals(obj, null))
+ throw new ArgumentNullException("obj");
+
+ var objKspVersion = obj as KspVersion;
+
+ if (objKspVersion != null)
+ return CompareTo(objKspVersion);
+ else
+ throw new ArgumentException("Object must be of type KspVersion.");
+ }
+
+ ///
+ /// Compares the current object to a specified object and returns an indication of
+ /// their relative values.
+ ///
+ /// An object to compare.
+ ///
+ /// A signed integer that indicates the relative values of the two objects, as shown in the following table.
+ ///
+ ///
+ /// Return value
+ /// Meaning
+ ///
+ /// -
+ /// Less than zero
+ ///
+ /// The current object is a version before .
+ ///
+ ///
+ /// -
+ /// Zero
+ ///
+ /// The current object is the same version as .
+ ///
+ ///
+ /// -
+ /// Greater than zero
+ ///
+ ///
+ /// The current object is a version subsequent to .
+ ///
+ ///
+ ///
+ ///
+ ///
+ public int CompareTo(KspVersion other)
+ {
+ if (ReferenceEquals(other, null))
+ throw new ArgumentNullException("other");
+
+ if (Equals(this, other))
+ return 0;
+
+ var majorCompare = _major.CompareTo(other._major);
+
+ if (majorCompare == 0)
+ {
+ var minorCompare = _minor.CompareTo(other._minor);
+
+ if (minorCompare == 0)
+ {
+ var patchCompare = _patch.CompareTo(other._patch);
+
+ return patchCompare == 0 ? _build.CompareTo(other._build) : patchCompare;
+ }
+ else
+ {
+ return minorCompare;
+ }
+ }
+ else
+ {
+ return majorCompare;
+ }
+ }
+
+ ///
+ /// Determines whether the first specified object is less than the second specified
+ /// object.
+ ///
+ /// The first object.
+ /// The second object.
+ ///
+ /// true if is less than ; otherwise, flase.
+ ///
+ public static bool operator <(KspVersion left, KspVersion right)
+ {
+ if (ReferenceEquals(left, null))
+ throw new ArgumentNullException("left");
+
+ if (ReferenceEquals(right, null))
+ throw new ArgumentNullException("right");
+
+ return left.CompareTo(right) < 0;
+ }
+
+ ///
+ /// Determines whether the first specified object is greater than the second
+ /// specified object.
+ ///
+ /// The first object.
+ /// The second object.
+ ///
+ /// true if is greater than ; otherwise, flase.
+ ///
+ public static bool operator >(KspVersion left, KspVersion right)
+ {
+ if (ReferenceEquals(left, null))
+ throw new ArgumentNullException("left");
+
+ if (ReferenceEquals(right, null))
+ throw new ArgumentNullException("right");
+
+ return left.CompareTo(right) > 0;
+ }
+
+ ///
+ /// Determines whether the first specified object is less than or equal to the second
+ /// specified object.
+ ///
+ /// The first object.
+ /// The second object.
+ ///
+ /// true if is less than or equal to ; otherwise, flase.
+ ///
+ public static bool operator <=(KspVersion left, KspVersion right)
+ {
+ if (ReferenceEquals(left, null))
+ throw new ArgumentNullException("left");
+
+ if (ReferenceEquals(right, null))
+ throw new ArgumentNullException("right");
+
+ return left.CompareTo(right) <= 0;
+ }
+
+ ///
+ /// Determines whether the first specified object is greater than or equal to the
+ /// second specified object.
+ ///
+ /// The first object.
+ /// The second object.
+ ///
+ /// true if is greater than or equal to ; otherwise, flase.
+ ///
+ public static bool operator >=(KspVersion left, KspVersion right)
+ {
+ if (ReferenceEquals(left, null))
+ throw new ArgumentNullException("left");
+
+ if (ReferenceEquals(right, null))
+ throw new ArgumentNullException("right");
+
+ return left.CompareTo(right) >= 0;
+ }
+ }
+
+ public sealed partial class KspVersion
+ {
+ private static string DeriveString(int major, int minor, int patch, int build)
+ {
+ var sb = new StringBuilder();
+
+ if (major != Undefined)
+ {
+ sb.Append(major);
+ }
+
+ if (minor != Undefined)
+ {
+ sb.Append(".");
+ sb.Append(minor);
+ }
+
+ if (patch != Undefined)
+ {
+ sb.Append(".");
+ sb.Append(patch);
+ }
+
+ if (build != Undefined)
+ {
+ sb.Append(".");
+ sb.Append(build);
+ }
+
+ var s = sb.ToString();
+
+ return s.Equals(string.Empty) ? null : s;
+ }
+ }
+
+ public sealed class KspVersionJsonConverter : JsonConverter
+ {
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ writer.WriteValue(value.ToString());
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ var value = reader.Value == null ? null : reader.Value.ToString();
+
+ switch (value)
+ {
+ case null:
+ return null;
+ case "any":
+ return KspVersion.Any;
+ default:
+ KspVersion result;
+ if (KspVersion.TryParse(value, out result))
+ return result;
+ else
+ throw new JsonException(string.Format("Could not parse KSP version: {0}", value));
+ }
+ }
+
+ public override bool CanConvert(Type objectType)
+ {
+ return objectType == typeof(KspVersion);
+ }
+ }
+}
diff --git a/Core/Versioning/KspVersionBound.cs b/Core/Versioning/KspVersionBound.cs
new file mode 100644
index 0000000000..a2516df490
--- /dev/null
+++ b/Core/Versioning/KspVersionBound.cs
@@ -0,0 +1,68 @@
+using System;
+
+namespace CKAN.Versioning
+{
+ public sealed partial class KspVersionBound
+ {
+ public static readonly KspVersionBound Unbounded = new KspVersionBound();
+
+ public KspVersion Value { get; private set; }
+ public bool Inclusive { get; private set; }
+
+ private readonly string _string;
+
+ public KspVersionBound()
+ : this(KspVersion.Any, true) { }
+
+ public KspVersionBound(KspVersion value, bool inclusive)
+ {
+ if (ReferenceEquals(value, null))
+ throw new ArgumentNullException("value");
+
+ Value = value;
+ Inclusive = inclusive;
+
+ _string = inclusive ? string.Format("[{0}]", value) : string.Format("({0})", value);
+ }
+
+ public override string ToString()
+ {
+ return _string;
+ }
+ }
+
+ public sealed partial class KspVersionBound : IEquatable
+ {
+ public bool Equals(KspVersionBound other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+ return Equals(Value, other.Value) && Inclusive == other.Inclusive;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ return obj is KspVersionBound && Equals((KspVersionBound) obj);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return ((Value != null ? Value.GetHashCode() : 0)*397) ^ Inclusive.GetHashCode();
+ }
+ }
+
+ public static bool operator ==(KspVersionBound left, KspVersionBound right)
+ {
+ return Equals(left, right);
+ }
+
+ public static bool operator !=(KspVersionBound left, KspVersionBound right)
+ {
+ return !Equals(left, right);
+ }
+ }
+}
diff --git a/Core/Versioning/KspVersionRange.cs b/Core/Versioning/KspVersionRange.cs
new file mode 100644
index 0000000000..e69609d393
--- /dev/null
+++ b/Core/Versioning/KspVersionRange.cs
@@ -0,0 +1,105 @@
+using System;
+using System.Text;
+
+namespace CKAN.Versioning
+{
+ public sealed partial class KspVersionRange
+ {
+ private readonly string _string;
+
+ public static readonly KspVersionRange Any =
+ new KspVersionRange(KspVersionBound.Unbounded, KspVersionBound.Unbounded);
+
+ public KspVersionBound Lower { get; private set; }
+ public KspVersionBound Upper { get; private set; }
+
+ public KspVersionRange(KspVersionBound lower, KspVersionBound upper)
+ {
+ if (ReferenceEquals(lower, null))
+ throw new ArgumentNullException("lower");
+
+ if (ReferenceEquals(upper, null))
+ throw new ArgumentNullException("upper");
+
+ Lower = lower;
+ Upper = upper;
+
+ _string = DeriveString(this);
+ }
+
+ public override string ToString()
+ {
+ return _string;
+ }
+
+ public bool IsSupersetOf(KspVersionRange other)
+ {
+ if (ReferenceEquals(other, null))
+ throw new ArgumentNullException("other");
+
+ var lowerIsOkay = Lower.Value.IsAny
+ || (Lower.Value < other.Lower.Value)
+ || (Lower.Value == other.Lower.Value && (Lower.Inclusive || !other.Lower.Inclusive));
+
+ var upperIsOkay = Upper.Value.IsAny
+ || (other.Upper.Value < Upper.Value)
+ || (other.Upper.Value == Upper.Value && (Upper.Inclusive || !other.Upper.Inclusive));
+
+ return lowerIsOkay && upperIsOkay;
+ }
+
+ private static string DeriveString(KspVersionRange versionRange)
+ {
+ var sb = new StringBuilder();
+
+ sb.Append(versionRange.Lower.Inclusive ? '[' : '(');
+
+ if (versionRange.Lower.Value != null)
+ sb.Append(versionRange.Lower.Value);
+
+ sb.Append(',');
+
+ if (versionRange.Upper.Value != null)
+ sb.Append(versionRange.Upper.Value);
+
+ sb.Append(versionRange.Upper.Inclusive ? ']' : ')');
+
+ return sb.ToString();
+ }
+ }
+
+ public sealed partial class KspVersionRange : IEquatable
+ {
+ public bool Equals(KspVersionRange other)
+ {
+ if (ReferenceEquals(null, other)) return false;
+ if (ReferenceEquals(this, other)) return true;
+ return Equals(Lower, other.Lower) && Equals(Upper, other.Upper);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ return obj is KspVersionRange && Equals((KspVersionRange) obj);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return ((Lower != null ? Lower.GetHashCode() : 0)*397) ^ (Upper != null ? Upper.GetHashCode() : 0);
+ }
+ }
+
+ public static bool operator ==(KspVersionRange left, KspVersionRange right)
+ {
+ return Equals(left, right);
+ }
+
+ public static bool operator !=(KspVersionRange left, KspVersionRange right)
+ {
+ return !Equals(left, right);
+ }
+ }
+}
diff --git a/Core/Win32Registry.cs b/Core/Win32Registry.cs
index f08bd8560b..6355597a39 100644
--- a/Core/Win32Registry.cs
+++ b/Core/Win32Registry.cs
@@ -10,6 +10,8 @@ public interface IWin32Registry
string AutoStartInstance { get; set; }
void SetRegistryToInstances(SortedList instances, string auto_start_instance);
IEnumerable> GetInstances();
+ string GetKSPBuilds();
+ void SetKSPBuilds(string buildMap);
}
public class Win32Registry : IWin32Registry
@@ -47,8 +49,6 @@ public void SetRegistryToInstances(SortedList instances, string aut
{
SetInstanceKeysTo(instance.number, instance.name, instance.path);
}
-
-
}
public IEnumerable> GetInstances()
@@ -56,6 +56,16 @@ public IEnumerable> GetInstances()
return Enumerable.Range(0, InstanceCount).Select(GetInstance).ToList();
}
+ public string GetKSPBuilds()
+ {
+ return GetRegistryValue(@"KSPBuilds", (string)null);
+ }
+
+ public void SetKSPBuilds(string buildMap)
+ {
+ SetRegistryValue(@"KSPBuilds", buildMap);
+ }
+
private void ConstructKey()
{
var key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"Software\CKAN");
diff --git a/Core/builds.json b/Core/builds.json
new file mode 100644
index 0000000000..b30c2ddf78
--- /dev/null
+++ b/Core/builds.json
@@ -0,0 +1,22 @@
+{
+ "builds": {
+ "464": "0.23.5.464",
+ "559": "0.24.2.559",
+ "642": "0.25.0.642",
+ "705": "0.90.0.705",
+ "830": "1.0.0.830",
+ "842": "1.0.2.842",
+ "861": "1.0.4.861",
+ "1024": "1.0.5.1024",
+ "1028": "1.0.5.1028",
+ "1172": "1.1.0.1172",
+ "1174": "1.1.0.1174",
+ "1183": "1.1.0.1183",
+ "1196": "1.1.0.1196",
+ "1203": "1.1.0.1203",
+ "1228": "1.1.0.1228",
+ "1230": "1.1.0.1230",
+ "1250": "1.1.1.1250",
+ "1260": "1.1.2.1260"
+ }
+}
diff --git a/GUI/GUIMod.cs b/GUI/GUIMod.cs
index 41901b86d1..b5ce33b6c5 100644
--- a/GUI/GUIMod.cs
+++ b/GUI/GUIMod.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Windows.Forms;
using System.Linq;
+using CKAN.Versioning;
namespace CKAN
{
@@ -45,7 +46,7 @@ public string Version
get { return IsInstalled ? InstalledVersion : LatestVersion; }
}
- public GUIMod(CkanModule mod, IRegistryQuerier registry, KSPVersion current_ksp_version)
+ public GUIMod(CkanModule mod, IRegistryQuerier registry, KspVersion current_ksp_version)
{
IsCKAN = mod is CkanModule;
//Currently anything which could alter these causes a full reload of the modlist
diff --git a/GUI/MainModList.cs b/GUI/MainModList.cs
index b9fe7c8f12..c753a3ec36 100644
--- a/GUI/MainModList.cs
+++ b/GUI/MainModList.cs
@@ -6,6 +6,7 @@
using System.Threading.Tasks;
using System.Reflection;
using System.Windows.Forms;
+using CKAN.Versioning;
namespace CKAN
{
@@ -144,7 +145,7 @@ private void _UpdateModsList(bool repo_updated)
{
log.Debug("Updating the mod list");
- KSPVersion version = CurrentInstance.Version();
+ KspVersion version = CurrentInstance.Version();
IRegistryQuerier registry = RegistryManager.Instance(CurrentInstance).registry;
var gui_mods = new HashSet(registry.Available(version)
.Select(m => new GUIMod(m, registry, version)));
@@ -361,7 +362,7 @@ public string ModDescriptionFilter
/// The version of the current KSP install
public async Task> ComputeChangeSetFromModList(
IRegistryQuerier registry, HashSet changeSet, ModuleInstaller installer,
- KSPVersion version)
+ KspVersion version)
{
var modules_to_install = new HashSet();
var modules_to_remove = new HashSet();
@@ -600,7 +601,7 @@ private bool IsModInFilter(GUIMod m)
public static Dictionary ComputeConflictsFromModList(IRegistryQuerier registry,
- IEnumerable change_set, KSPVersion ksp_version)
+ IEnumerable change_set, KspVersion ksp_version)
{
var modules_to_install = new HashSet();
var modules_to_remove = new HashSet();
diff --git a/Netkan/Sources/Avc/AvcVersion.cs b/Netkan/Sources/Avc/AvcVersion.cs
index 62f4642d22..9acbb8d3e7 100644
--- a/Netkan/Sources/Avc/AvcVersion.cs
+++ b/Netkan/Sources/Avc/AvcVersion.cs
@@ -1,3 +1,4 @@
+using CKAN.Versioning;
using Newtonsoft.Json;
namespace CKAN.NetKAN.Sources.Avc
@@ -13,12 +14,12 @@ public class AvcVersion
public Version version;
[JsonConverter(typeof (JsonAvcToKspVersion))]
- public KSPVersion ksp_version;
+ public KspVersion ksp_version;
[JsonConverter(typeof (JsonAvcToKspVersion))]
- public KSPVersion ksp_version_min;
+ public KspVersion ksp_version_min;
[JsonConverter(typeof (JsonAvcToKspVersion))]
- public KSPVersion ksp_version_max;
+ public KspVersion ksp_version_max;
}
}
diff --git a/Netkan/Sources/Avc/JsonAvcToKspVersion.cs b/Netkan/Sources/Avc/JsonAvcToKspVersion.cs
index e4f68f44f3..b7b4178e15 100644
--- a/Netkan/Sources/Avc/JsonAvcToKspVersion.cs
+++ b/Netkan/Sources/Avc/JsonAvcToKspVersion.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using CKAN.Versioning;
using log4net;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -63,7 +64,7 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
string version;
if (int.TryParse(major, out integer) && integer == AvcWildcard)
{
- version = null;
+ return KspVersion.Any;
}
else if (int.TryParse(minor, out integer) && integer == AvcWildcard)
{
@@ -79,7 +80,7 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
}
Log.DebugFormat(" extracted version: {0}", version);
- var result = new KSPVersion(version);
+ var result = KspVersion.Parse(version);
Log.DebugFormat(" generated result: {0}", result);
return result;
}
diff --git a/Netkan/Sources/Spacedock/SDVersion.cs b/Netkan/Sources/Spacedock/SDVersion.cs
index ccbe8d7a57..cdab7f18c9 100644
--- a/Netkan/Sources/Spacedock/SDVersion.cs
+++ b/Netkan/Sources/Spacedock/SDVersion.cs
@@ -1,5 +1,6 @@
using System;
using System.Text.RegularExpressions;
+using CKAN.Versioning;
using log4net;
using Newtonsoft.Json;
@@ -13,7 +14,7 @@ public class SDVersion
[JsonConverter(typeof(JsonConvertKSPVersion))]
[JsonProperty("game_version")]
- public KSPVersion KSP_version;
+ public KspVersion KSP_version;
public string changelog;
@@ -51,7 +52,7 @@ public override object ReadJson(
string raw_version = reader.Value.ToString();
- return new KSPVersion( ExpandVersionIfNeeded(raw_version) );
+ return KspVersion.Parse(ExpandVersionIfNeeded(raw_version));
}
///
diff --git a/Netkan/Transformers/AvcTransformer.cs b/Netkan/Transformers/AvcTransformer.cs
index 4502b5f590..d0f3da555d 100644
--- a/Netkan/Transformers/AvcTransformer.cs
+++ b/Netkan/Transformers/AvcTransformer.cs
@@ -5,6 +5,7 @@
using CKAN.NetKAN.Model;
using CKAN.NetKAN.Services;
using CKAN.NetKAN.Sources.Avc;
+using CKAN.Versioning;
using log4net;
using Newtonsoft.Json;
@@ -87,8 +88,8 @@ public Metadata Transform(Metadata metadata)
var existingKspMinStr = (string)json["ksp_version_min"] ?? (string)json["ksp_version"];
var existingKspMaxStr = (string)json["ksp_version_max"] ?? (string)json["ksp_version"];
- var existingKspMin = existingKspMinStr == null ? null : new KSPVersion(existingKspMinStr);
- var existingKspMax = existingKspMaxStr == null ? null : new KSPVersion(existingKspMaxStr);
+ var existingKspMin = existingKspMinStr == null ? null : KspVersion.Parse(existingKspMinStr);
+ var existingKspMax = existingKspMaxStr == null ? null : KspVersion.Parse(existingKspMaxStr);
// Get the minimum and maximum KSP versions that are in the AVC file.
// Use specific KSP version if min/max don't exist.
@@ -97,8 +98,8 @@ public Metadata Transform(Metadata metadata)
// Now calculate the minimum and maximum KSP versions between both the existing metadata and the
// AVC file.
- var kspMins = new List();
- var kspMaxes = new List();
+ var kspMins = new List();
+ var kspMaxes = new List();
if (existingKspMin != null)
kspMins.Add(existingKspMin);
diff --git a/Tests/Core/KSPManager.cs b/Tests/Core/KSPManager.cs
index 0ee3ec463b..66d473ffc6 100644
--- a/Tests/Core/KSPManager.cs
+++ b/Tests/Core/KSPManager.cs
@@ -176,6 +176,7 @@ public FakeWin32Registry(List> instances, string auto_star
}
public List> Instances { get; set; }
+ public string BuildMap { get; set; }
public int InstanceCount
{
@@ -209,5 +210,15 @@ public IEnumerable> GetInstances()
{
return Instances;
}
+
+ public string GetKSPBuilds()
+ {
+ return BuildMap;
+ }
+
+ public void SetKSPBuilds(string buildMap)
+ {
+ BuildMap = buildMap;
+ }
}
}
\ No newline at end of file
diff --git a/Tests/Core/Net/Repo.cs b/Tests/Core/Net/Repo.cs
index 1edab7ce63..b2185d7ac5 100644
--- a/Tests/Core/Net/Repo.cs
+++ b/Tests/Core/Net/Repo.cs
@@ -1,4 +1,5 @@
using CKAN;
+using CKAN.Versioning;
using NUnit.Framework;
using Tests.Data;
@@ -33,7 +34,7 @@ public void UpdateRegistryTarGz()
CKAN.Repo.UpdateRegistry(TestData.TestKANTarGz(), registry, ksp.KSP, new NullUser());
// Test we've got an expected module.
- CkanModule far = registry.LatestAvailable("FerramAerospaceResearch", new KSPVersion("0.25.0"));
+ CkanModule far = registry.LatestAvailable("FerramAerospaceResearch", KspVersion.Parse("0.25.0"));
Assert.AreEqual("v0.14.3.2", far.version.ToString());
}
@@ -44,7 +45,7 @@ public void UpdateRegistryZip()
CKAN.Repo.UpdateRegistry(TestData.TestKANZip(), registry, ksp.KSP, new NullUser());
// Test we've got an expected module.
- CkanModule far = registry.LatestAvailable("FerramAerospaceResearch", new KSPVersion("0.25.0"));
+ CkanModule far = registry.LatestAvailable("FerramAerospaceResearch", KspVersion.Parse("0.25.0"));
Assert.AreEqual("v0.14.3.2", far.version.ToString());
}
diff --git a/Tests/Core/Registry/Registry.cs b/Tests/Core/Registry/Registry.cs
index d7499d388f..68a5e02e83 100644
--- a/Tests/Core/Registry/Registry.cs
+++ b/Tests/Core/Registry/Registry.cs
@@ -1,5 +1,6 @@
using System.Transactions;
using CKAN;
+using CKAN.Versioning;
using NUnit.Framework;
using Tests.Data;
@@ -10,8 +11,8 @@ public class Registry
{
private static readonly CkanModule module = TestData.kOS_014_module();
private static readonly string identifier = module.identifier;
- private static readonly KSPVersion v0_24_2 = new KSPVersion("0.24.2");
- private static readonly KSPVersion v0_25_0 = new KSPVersion("0.25.0");
+ private static readonly KspVersion v0_24_2 = KspVersion.Parse("0.24.2");
+ private static readonly KspVersion v0_25_0 = KspVersion.Parse("0.25.0");
private CKAN.Registry registry;
diff --git a/Tests/Core/Relationships/RelationshipResolver.cs b/Tests/Core/Relationships/RelationshipResolver.cs
index 3fbac788a8..4db2578198 100644
--- a/Tests/Core/Relationships/RelationshipResolver.cs
+++ b/Tests/Core/Relationships/RelationshipResolver.cs
@@ -5,6 +5,9 @@
using NUnit.Framework;
using Tests.Data;
using System.IO;
+using CKAN.Versioning;
+using Tests.Core.Types;
+using RelationshipDescriptor = CKAN.RelationshipDescriptor;
using Version = CKAN.Version;
namespace Tests.Core.Relationships
@@ -694,7 +697,7 @@ public void Constructor_WithRegistryThatHasRequiredModuleRemoved_Throws()
{
var list = new List();
var mod = generator.GeneratorRandomModule();
- mod.ksp_version = new KSPVersion("0.10");
+ mod.ksp_version = KspVersion.Parse("0.10");
list.Add(mod.identifier);
registry.AddAvailable(mod);
registry.RemoveAvailable(mod);
@@ -803,7 +806,7 @@ public void AutodetectedCanSatisfyRelationships()
new CKAN.CkanModule[] { mod },
RelationshipResolver.DefaultOpts(),
registry,
- new KSPVersion("1.0.0")
+ KspVersion.Parse("1.0.0")
);
}
}
diff --git a/Tests/Core/Types/GameComparator.cs b/Tests/Core/Types/GameComparator.cs
index ffbda431a8..aa953e4734 100644
--- a/Tests/Core/Types/GameComparator.cs
+++ b/Tests/Core/Types/GameComparator.cs
@@ -1,4 +1,5 @@
using System;
+using CKAN.Versioning;
using NUnit.Framework;
using Tests.Data;
using log4net;
@@ -8,7 +9,7 @@ namespace Tests.Core.Types
[TestFixture]
public class GameComparator
{
- static readonly CKAN.KSPVersion gameVersion = new CKAN.KSPVersion("1.0.4");
+ static readonly KspVersion gameVersion = KspVersion.Parse("1.0.4");
CKAN.CkanModule gameMod;
[SetUp]
@@ -28,7 +29,7 @@ public void TotallyCompatible(Type type, bool expected)
// Mark the mod as being for 1.0.4
gameMod.ksp_version = gameMod.ksp_version_min = gameMod.ksp_version_max
- = new CKAN.KSPVersion("1.0.4");
+ = KspVersion.Parse("1.0.4");
// Now test!
Assert.AreEqual(expected, comparator.Compatible(gameVersion, gameMod));
@@ -44,7 +45,7 @@ public void GenerallySafeLax(Type type, bool expected)
// We're going to tweak compatibly to mark the mod as being for 1.0.3
gameMod.ksp_version = gameMod.ksp_version_min = gameMod.ksp_version_max
- = new CKAN.KSPVersion("1.0.3");
+ = KspVersion.Parse("1.0.3");
// Now test!
Assert.AreEqual(expected, comparator.Compatible(gameVersion, gameMod));
@@ -60,7 +61,7 @@ public void GenerallySafeStrict(Type type, bool expected)
// We're going to tweak compatibly to mark the mod as being for 1.0.3 ONLY
gameMod.ksp_version = gameMod.ksp_version_min = gameMod.ksp_version_max
- = new CKAN.KSPVersion("1.0.3");
+ = KspVersion.Parse("1.0.3");
gameMod.ksp_version_strict = true;
diff --git a/Tests/Core/Types/KSPVersion.cs b/Tests/Core/Types/KSPVersion.cs
deleted file mode 100644
index eeaa4a2b40..0000000000
--- a/Tests/Core/Types/KSPVersion.cs
+++ /dev/null
@@ -1,116 +0,0 @@
-using NUnit.Framework;
-
-namespace Tests.Core.Types
-{
- [TestFixture]
- public class KSPVersion
- {
- private static void BadTarget()
- {
- var any = new CKAN.KSPVersion(null);
- var vshort = new CKAN.KSPVersion("0.23");
-
- // We can't ask if something targets a non-real (short) version.
- any.Targets(vshort);
- }
-
- [Test]
- public void MinMax()
- {
- var min = new CKAN.KSPVersion("0.23");
- var max = new CKAN.KSPVersion("0.23");
-
- min = min.ToLongMin();
- max = max.ToLongMax();
-
- Assert.IsTrue(min.Version() == "0.23.0");
- Assert.IsTrue(max.Version() == "0.23.99"); // Ugh, magic version number.
-
- Assert.IsTrue(min < max);
- Assert.IsTrue(max > min);
- }
-
- [Test]
- public void Strings()
- {
- var any = new CKAN.KSPVersion(null);
- var vshort = new CKAN.KSPVersion("0.23");
- var vlong = new CKAN.KSPVersion("0.23.5");
-
- Assert.AreEqual(any.ToString(), null);
- Assert.AreEqual(vshort.ToString(), "0.23");
- Assert.AreEqual(vlong.ToString(), "0.23.5");
- }
-
- [Test]
- public void Targets()
- {
- var any = new CKAN.KSPVersion(null);
- var vshort = new CKAN.KSPVersion("0.23");
- var vlong = new CKAN.KSPVersion("0.23.5");
-
- Assert.IsTrue(any.Targets(vlong));
- Assert.IsTrue(vshort.Targets(vlong));
- Assert.IsTrue(vlong.Targets(vlong));
-
- Assert.That(BadTarget, Throws.Exception);
- }
-
- [Test]
- public void TargetsString()
- {
- var any = new CKAN.KSPVersion(null);
- var vshort = new CKAN.KSPVersion("0.23");
- var vlong = new CKAN.KSPVersion("0.23.5");
- var slong = "0.23.5";
-
- // Same tests as above, but now testing against a string. :)
- Assert.IsTrue(any.Targets(slong));
- Assert.IsTrue(vshort.Targets(slong));
- Assert.IsTrue(vlong.Targets(slong));
-
- // And lets test some things that should fail.
- var too_big = new CKAN.KSPVersion("1.0.4");
-
- Assert.IsFalse(vshort.Targets(too_big));
- Assert.IsFalse(vlong.Targets(too_big));
- }
-
- [Test]
- public void Types()
- {
- var v1 = new CKAN.KSPVersion("any");
- Assert.IsTrue(v1.IsAny());
- Assert.IsFalse(v1.IsNotAny());
-
- var v2 = new CKAN.KSPVersion(null); // Same as "any"
- Assert.IsTrue(v2.IsAny());
- Assert.IsFalse(v2.IsNotAny());
-
- var vshort = new CKAN.KSPVersion("0.25");
- Assert.IsTrue(vshort.IsShortVersion());
- Assert.IsFalse(vshort.IsLongVersion());
- Assert.IsFalse(vshort.IsAny());
-
- var vlong = new CKAN.KSPVersion("0.25.2");
- Assert.IsTrue(vlong.IsLongVersion());
- Assert.IsFalse(vlong.IsShortVersion());
- Assert.IsFalse(vlong.IsAny());
- }
-
- [Test]
- public void MissingZeros()
- {
- var v1 = new CKAN.KSPVersion(".23.5");
- Assert.IsInstanceOf(v1);
- Assert.AreEqual("0.23.5", v1.Version());
-
- var v0_23_5 = new CKAN.KSPVersion("0.23.5");
- Assert.IsTrue(v1.Targets(v0_23_5));
-
- // For when Squad makes what they think is a full release. ;)
- var v1_0_0 = new CKAN.KSPVersion("1.0.0");
- Assert.IsTrue(v1_0_0 > v0_23_5);
- }
- }
-}
\ No newline at end of file
diff --git a/Tests/Core/Versioning/KspVersionBoundTests.cs b/Tests/Core/Versioning/KspVersionBoundTests.cs
new file mode 100644
index 0000000000..8dd7a8249f
--- /dev/null
+++ b/Tests/Core/Versioning/KspVersionBoundTests.cs
@@ -0,0 +1,150 @@
+using CKAN.Versioning;
+using NUnit.Framework;
+
+namespace Tests.Core.Versioning
+{
+ [TestFixture]
+ public sealed class KspVersionBoundTests
+ {
+ private static readonly object[] EqualityCases =
+ {
+ new object[]
+ {
+ new KspVersionBound(),
+ new KspVersionBound(),
+ true
+ },
+ new object[]
+ {
+ new KspVersionBound(new KspVersion(1), false),
+ new KspVersionBound(new KspVersion(1), false),
+ true
+ },
+ new object[]
+ {
+ new KspVersionBound(new KspVersion(1), false),
+ new KspVersionBound(new KspVersion(1), true),
+ false
+ },
+ new object[]
+ {
+ new KspVersionBound(new KspVersion(1), false),
+ new KspVersionBound(new KspVersion(1, 2), false),
+ false
+ },
+ new object[]
+ {
+ new KspVersionBound(new KspVersion(), false),
+ new KspVersionBound(new KspVersion(), true)
+ }
+ };
+
+ [Test]
+ public void ParameterlessCtorWorksCorrectly()
+ {
+ // Act
+ var result = new KspVersionBound();
+
+ // Assert
+ Assert.AreEqual(KspVersion.Any, result.Value);
+ Assert.IsTrue(result.Inclusive);
+ }
+
+ [Test]
+ public void ParameterfulCtorWorksCorrectly()
+ {
+ // Act
+ var result = new KspVersionBound(new KspVersion(1, 2, 3, 4), true);
+
+ // Assert
+ Assert.AreEqual(new KspVersion(1, 2, 3, 4), result.Value);
+ Assert.IsTrue(result.Inclusive);
+ }
+
+ [Test]
+ public void ParameterfulCtorThrowsOnInvalidInput()
+ {
+ // Act
+ // ReSharper disable once ObjectCreationAsStatement
+ TestDelegate act = () => new KspVersionBound(null, false);
+
+ // Assert
+ Assert.That(act, Throws.ArgumentException);
+ }
+
+ [TestCaseSource("EqualityCases")]
+ public void EqualityWorksCorrectly(KspVersionBound vb1, KspVersionBound vb2, bool areEqual)
+ {
+ // Act
+ var genericEquals = vb1.Equals(vb2);
+ var nonGenericEquals = vb1.Equals((object)vb2);
+ var equalsOperator = vb1 == vb2;
+ var notEqualsOperator = vb1 != vb2;
+ var reverseEqualsOperator = vb2 == vb1;
+ var reverseNotEqualsOperator = vb2 != vb1;
+
+ // Assert
+ Assert.AreEqual(areEqual, genericEquals);
+ Assert.AreEqual(areEqual, nonGenericEquals);
+ Assert.AreEqual(areEqual, equalsOperator);
+ Assert.AreNotEqual(areEqual, notEqualsOperator);
+ Assert.AreEqual(areEqual, reverseEqualsOperator);
+ Assert.AreNotEqual(areEqual, reverseNotEqualsOperator);
+ }
+
+ [Test]
+ public void NullEqualityWorksCorrectly()
+ {
+ // Act
+ // ReSharper disable ConditionIsAlwaysTrueOrFalse
+ var genericEquals = new KspVersionBound().Equals(null);
+ var nonGenericEquals = new KspVersionBound().Equals((object)null);
+ var equalsOperatorNullLeft = null == new KspVersionBound();
+ var equalsOperatorNullRight = new KspVersionBound() == null;
+ var notEqualsOperatorNullLeft = null != new KspVersionBound();
+ var notEqualsOperatorNullRight = new KspVersionBound() != null;
+ // ReSharper restore ConditionIsAlwaysTrueOrFalse
+
+ // ASsert
+ Assert.IsFalse(genericEquals);
+ Assert.IsFalse(nonGenericEquals);
+ Assert.IsFalse(equalsOperatorNullLeft);
+ Assert.IsFalse(equalsOperatorNullRight);
+ Assert.IsTrue(notEqualsOperatorNullLeft);
+ Assert.IsTrue(notEqualsOperatorNullRight);
+ }
+
+ [Test]
+ public void ReferenecEqualityWorksCorrectly()
+ {
+ // Arrange
+ var sut = new KspVersionBound();
+
+ // Act
+ var genericEquals = sut.Equals(sut);
+ var nonGenericEquals = sut.Equals((object)sut);
+
+ // Assert
+ Assert.IsTrue(genericEquals);
+ Assert.IsTrue(nonGenericEquals);
+ }
+
+ [Test]
+ public void GetHashCodeDoesNotThrow(
+ [Random(0, int.MaxValue, 1)]int major,
+ [Random(0, int.MaxValue, 1)]int minor,
+ [Random(0, int.MaxValue, 1)]int patch,
+ [Random(0, int.MaxValue, 1)]int build,
+ [Values(false, true)]bool inclusive
+ )
+ {
+ // Act
+ // ReSharper disable once ReturnValueOfPureMethodIsNotUsed
+ TestDelegate act =
+ () => new KspVersionBound(new KspVersion(major, minor, patch, build), inclusive).GetHashCode();
+
+ // Assert
+ Assert.That(act, Throws.Nothing);
+ }
+ }
+}
diff --git a/Tests/Core/Versioning/KspVersionTests.cs b/Tests/Core/Versioning/KspVersionTests.cs
new file mode 100644
index 0000000000..d6b67cc2c7
--- /dev/null
+++ b/Tests/Core/Versioning/KspVersionTests.cs
@@ -0,0 +1,436 @@
+using System;
+using CKAN.Versioning;
+using NUnit.Framework;
+
+#pragma warning disable 219, 414
+
+namespace Tests.Core.Versioning
+{
+ [TestFixture]
+ public class KspVersionTests
+ {
+ private static readonly object[] ParseCases =
+ {
+ new object[] { "1", new KspVersion(1) },
+ new object[] { "1.2", new KspVersion(1, 2) },
+ new object[] { "1.2.3", new KspVersion(1, 2, 3) },
+ new object[] { "1.2.3.4", new KspVersion(1, 2, 3, 4) }
+ };
+
+ private static readonly object[] ParseFailureCases =
+ {
+ new object[] { null },
+ new object[] { "" },
+ new object[] { "1.2.3.4.5" },
+ new object[] { "1.2.3.A" },
+ new object[] { "1.2.3.-1" },
+ new object[] { "9876543210.1.2.3" },
+ new object[] { "1.9876543210.2.3" },
+ new object[] { "1.2.9876543210.3" },
+ new object[] { "1.2.3.9876543210" }
+ };
+
+ private static readonly object[] EqualityCases =
+ {
+ new object[] { new KspVersion(), null, false },
+ new object[] { new KspVersion(), new KspVersion(), true },
+ new object[] { new KspVersion(1), new KspVersion(1), true },
+ new object[] { new KspVersion(1, 2), new KspVersion(1, 2), true},
+ new object[] { new KspVersion(1, 2, 3), new KspVersion(1, 2, 3), true},
+ new object[] { new KspVersion(1, 2, 3, 4), new KspVersion(1, 2, 3, 4), true},
+ new object[] { new KspVersion(), new KspVersion(1), false },
+ new object[] { new KspVersion(1), new KspVersion(1, 2), false },
+ new object[] { new KspVersion(1, 2), new KspVersion(1, 2, 3), false},
+ new object[] { new KspVersion(1, 2, 3), new KspVersion(1, 2, 3, 4), false},
+ new object[] { new KspVersion(1, 2, 3, 4), new KspVersion(1, 2, 3, 5), false}
+ };
+
+ private static readonly object[] CompareToCases =
+ {
+ new object[] { new KspVersion(), new KspVersion(), 0 },
+ new object[] { new KspVersion(1), new KspVersion(1), 0 },
+ new object[] { new KspVersion(1, 2), new KspVersion(1, 2), 0 },
+ new object[] { new KspVersion(1, 2, 3), new KspVersion(1, 2, 3), 0 },
+ new object[] { new KspVersion(1, 2, 3, 4), new KspVersion(1, 2, 3, 4), 0 },
+ new object[] { new KspVersion(), new KspVersion(1), -1 },
+ new object[] { new KspVersion(1), new KspVersion(1, 2), -1 },
+ new object[] { new KspVersion(1, 2), new KspVersion(1, 2, 3), -1 },
+ new object[] { new KspVersion(1, 2, 3), new KspVersion(1, 2, 3, 4), -1 },
+ new object[] { new KspVersion(1, 2, 3, 4), new KspVersion(1, 2, 3, 5), -1 },
+ new object[] { new KspVersion(1), new KspVersion(), 1 },
+ new object[] { new KspVersion(1, 2), new KspVersion(1), 1 },
+ new object[] { new KspVersion(1, 2, 3), new KspVersion(1, 2), 1 },
+ new object[] { new KspVersion(1, 2, 3, 5), new KspVersion(1, 2, 3, 4), 1}
+ };
+
+ private static readonly object[] ToVersionRangeWorksCorrectlyCases =
+ {
+ new object[] { new KspVersion(), KspVersionRange.Any },
+ new object[]
+ {
+ new KspVersion(1),
+ new KspVersionRange(
+ new KspVersionBound(new KspVersion(1, 0, 0, 0), inclusive: true),
+ new KspVersionBound(new KspVersion(2, 0, 0, 0), inclusive: false)
+ )
+ },
+ new object[]
+ {
+ new KspVersion(1, 2),
+ new KspVersionRange(
+ new KspVersionBound(new KspVersion(1, 2, 0, 0), inclusive: true),
+ new KspVersionBound(new KspVersion(1, 3, 0, 0), inclusive: false)
+ )
+ },
+ new object[]
+ {
+ new KspVersion(1, 2, 3),
+ new KspVersionRange(
+ new KspVersionBound(new KspVersion(1, 2, 3, 0), inclusive: true),
+ new KspVersionBound(new KspVersion(1, 2, 4, 0), inclusive: false)
+ )
+ },
+ new object[]
+ {
+ new KspVersion(1, 2, 3, 4),
+ new KspVersionRange(
+ new KspVersionBound(new KspVersion(1, 2, 3, 4), inclusive: true),
+ new KspVersionBound(new KspVersion(1, 2, 3, 4), inclusive: true)
+ )
+ }
+ };
+
+ [Test]
+ public void ParameterlessCtorWorksCorrectly()
+ {
+ // Act
+ var result = new KspVersion();
+
+ // Assert
+ Assert.AreEqual(-1, result.Major);
+ Assert.AreEqual(-1, result.Minor);
+ Assert.AreEqual(-1, result.Patch);
+ Assert.AreEqual(-1, result.Build);
+
+ Assert.IsFalse(result.IsMajorDefined);
+ Assert.IsFalse(result.IsMinorDefined);
+ Assert.IsFalse(result.IsPatchDefined);
+ Assert.IsFalse(result.IsBuildDefined);
+
+ Assert.IsFalse(result.IsFullyDefined);
+ Assert.IsTrue(result.IsAny);
+
+ Assert.AreEqual(null, result.ToString());
+ }
+
+ [Test]
+ public void SingleParameterCtorWorksCorrectly()
+ {
+ // Act
+ var result = new KspVersion(1);
+
+ // Assert
+ Assert.AreEqual(1, result.Major);
+ Assert.AreEqual(-1, result.Minor);
+ Assert.AreEqual(-1, result.Patch);
+ Assert.AreEqual(-1, result.Build);
+
+ Assert.IsTrue(result.IsMajorDefined);
+ Assert.IsFalse(result.IsMinorDefined);
+ Assert.IsFalse(result.IsPatchDefined);
+ Assert.IsFalse(result.IsBuildDefined);
+
+ Assert.IsFalse(result.IsFullyDefined);
+ Assert.IsFalse(result.IsAny);
+
+ Assert.AreEqual("1", result.ToString());
+ }
+
+ [TestCase(-1)]
+ public void SingleParameterCtorThrowsOnInvalidParameters(int major)
+ {
+ // Act
+ // ReSharper disable once ObjectCreationAsStatement
+ TestDelegate act = () => new KspVersion(major);
+
+ // Assert
+ Assert.That(act, Throws.Exception.InstanceOf());
+ }
+
+ [Test]
+ public void DoubleParameterCtorWorksCorrectly()
+ {
+ // Act
+ var result = new KspVersion(1, 2);
+
+ // Assert
+ Assert.AreEqual(1, result.Major);
+ Assert.AreEqual(2, result.Minor);
+ Assert.AreEqual(-1, result.Patch);
+ Assert.AreEqual(-1, result.Build);
+
+ Assert.IsTrue(result.IsMajorDefined);
+ Assert.IsTrue(result.IsMinorDefined);
+ Assert.IsFalse(result.IsPatchDefined);
+ Assert.IsFalse(result.IsBuildDefined);
+
+ Assert.IsFalse(result.IsFullyDefined);
+ Assert.IsFalse(result.IsAny);
+
+ Assert.AreEqual("1.2", result.ToString());
+ }
+
+ [TestCase(-1, 0)]
+ [TestCase(0, -1)]
+ public void DoubleParameterCtorThrowsOnInvalidParameters(int major, int minor)
+ {
+ // Act
+ // ReSharper disable once ObjectCreationAsStatement
+ TestDelegate act = () => new KspVersion(major, minor);
+
+ // Assert
+ Assert.That(act, Throws.Exception.InstanceOf());
+ }
+
+ [Test]
+ public void TripleParameterCtorWorksCorrectly()
+ {
+ // Act
+ var result = new KspVersion(1, 2, 3);
+
+ // Assert
+ Assert.AreEqual(1, result.Major);
+ Assert.AreEqual(2, result.Minor);
+ Assert.AreEqual(3, result.Patch);
+ Assert.AreEqual(-1, result.Build);
+
+ Assert.IsTrue(result.IsMajorDefined);
+ Assert.IsTrue(result.IsMinorDefined);
+ Assert.IsTrue(result.IsPatchDefined);
+ Assert.IsFalse(result.IsBuildDefined);
+
+ Assert.IsFalse(result.IsFullyDefined);
+ Assert.IsFalse(result.IsAny);
+
+ Assert.AreEqual("1.2.3", result.ToString());
+ }
+
+ [TestCase(-1, 0, 0)]
+ [TestCase(0, -1, 0)]
+ [TestCase(0, 0, -1)]
+ public void TripleParameterCtorThrowsOnInvalidParameters(int major, int minor, int patch)
+ {
+ // Act
+ // ReSharper disable once ObjectCreationAsStatement
+ TestDelegate act = () => new KspVersion(major, minor, patch);
+
+ // Assert
+ Assert.That(act, Throws.Exception.InstanceOf());
+ }
+
+ [Test]
+ public void QuadrupleParameterCtorWorksCorrectly()
+ {
+ // Act
+ var result = new KspVersion(1, 2, 3, 4);
+
+ // Assert
+ Assert.AreEqual(1, result.Major);
+ Assert.AreEqual(2, result.Minor);
+ Assert.AreEqual(3, result.Patch);
+ Assert.AreEqual(4, result.Build);
+
+ Assert.IsTrue(result.IsMajorDefined);
+ Assert.IsTrue(result.IsMinorDefined);
+ Assert.IsTrue(result.IsPatchDefined);
+ Assert.IsTrue(result.IsBuildDefined);
+
+ Assert.IsTrue(result.IsFullyDefined);
+ Assert.IsFalse(result.IsAny);
+
+ Assert.AreEqual("1.2.3.4", result.ToString());
+ }
+
+ [TestCase(-1, 0, 0, 0)]
+ [TestCase(0, -1, 0, 0)]
+ [TestCase(0, 0, -1, 0)]
+ [TestCase(0, 0, 0, -1)]
+ public void QuadrupleParameterCtorThrowsOnInvalidParameters(int major, int minor, int patch, int build)
+ {
+ // Act
+ // ReSharper disable once ObjectCreationAsStatement
+ TestDelegate act = () => new KspVersion(major, minor, patch, build);
+
+ // Assert
+ Assert.That(act, Throws.Exception.InstanceOf());
+ }
+
+ [TestCaseSource("ParseCases")]
+ public void ParseWorksCorrectly(string s, KspVersion version)
+ {
+ // Act
+ var result = KspVersion.Parse(s);
+
+ // Assert
+ Assert.AreEqual(version, result);
+ Assert.AreEqual(s, result.ToString());
+ }
+
+ [TestCaseSource("ParseFailureCases")]
+ public void ParseThrowsExceptionOnInvalidParameter(string s)
+ {
+ // Act
+ // ReSharper disable once ObjectCreationAsStatement
+ TestDelegate act = () => KspVersion.Parse(s);
+
+ // Assert
+ Assert.That(act, Throws.Exception);
+ }
+
+ [TestCaseSource("ToVersionRangeWorksCorrectlyCases")]
+ public void ToVersionRangeWorksCorrectly(KspVersion version, KspVersionRange expectedRange)
+ {
+ // Act
+ var result = version.ToVersionRange();
+
+ // Assert
+ Assert.AreEqual(expectedRange, result);
+ }
+
+ [TestCaseSource("ParseCases")]
+ public void TryParseWorksCorrectly(string s, KspVersion version)
+ {
+ // Act
+ KspVersion result;
+ var success = KspVersion.TryParse(s, out result);
+
+ // Assert
+ Assert.IsTrue(success);
+ Assert.AreEqual(version, result);
+ Assert.AreEqual(s, result.ToString());
+ }
+
+ [TestCaseSource("ParseFailureCases")]
+ public void TryParseReturnsFalseOnInvalidParameter(string s)
+ {
+ // Act
+ KspVersion result;
+ var success = KspVersion.TryParse(s, out result);
+
+ // Assert
+ Assert.IsFalse(success);
+ }
+
+ [TestCaseSource("EqualityCases")]
+ public void EqualityWorksCorrectly(KspVersion a, KspVersion b, bool areEqual)
+ {
+ // Act
+ var genericEquality = a.Equals(b);
+ var nonGenericEquality = a.Equals((object)b);
+ var operatorEquality = a == b;
+ var operatorInequality = a != b;
+ var genericReferenceEquality = a.Equals(a);
+ var nonGenericRefereneEquality = a.Equals((object)a);
+
+ // Assert
+ Assert.AreEqual(areEqual, genericEquality);
+ Assert.AreEqual(areEqual, nonGenericEquality);
+ Assert.AreEqual(areEqual, operatorEquality);
+ Assert.AreNotEqual(areEqual, operatorInequality);
+ Assert.IsTrue(genericReferenceEquality);
+ Assert.IsTrue(nonGenericRefereneEquality);
+ }
+
+ [TestCaseSource("CompareToCases")]
+ public void CompareToWorksCorrectly(KspVersion v1, KspVersion v2, int comparison)
+ {
+ // Act
+ var genericCompareTo = v1.CompareTo(v2);
+ var nonGenericCompareTo = v1.CompareTo((object)v2);
+ var lessThanOperator = v1 < v2;
+ var lessThanOrEqualOperator = v1 <= v2;
+ var greaterThanOperator = v1 > v2;
+ var greaterThanOrEqualOperator = v1 >= v2;
+
+ var reverseGenericCompareTo = v2.CompareTo(v1);
+ var reverseNonGenericCompareTo = v2.CompareTo((object)v1);
+ var reverseLessThanOperator = v2 < v1;
+ var reverseLessThanOrEqualOperator = v2 <= v1;
+ var reverseGreaterThanOperator = v2 > v1;
+ var reverseGreaterThanOrEqualOperator = v2 >= v1;
+
+ // Assert
+ Assert.AreEqual(Math.Sign(comparison), Math.Sign(genericCompareTo));
+ Assert.AreEqual(Math.Sign(comparison), Math.Sign(nonGenericCompareTo));
+ Assert.AreEqual(comparison < 0, lessThanOperator);
+ Assert.AreEqual(comparison <= 0, lessThanOrEqualOperator);
+ Assert.AreEqual(comparison > 0, greaterThanOperator);
+ Assert.AreEqual(comparison >= 0, greaterThanOrEqualOperator);
+ Assert.AreEqual(-Math.Sign(comparison), Math.Sign(reverseGenericCompareTo));
+ Assert.AreEqual(-Math.Sign(comparison), Math.Sign(reverseNonGenericCompareTo));
+ Assert.AreEqual(comparison > 0, reverseLessThanOperator);
+ Assert.AreEqual(comparison >= 0, reverseLessThanOrEqualOperator);
+ Assert.AreEqual(comparison < 0, reverseGreaterThanOperator);
+ Assert.AreEqual(comparison <= 0, reverseGreaterThanOrEqualOperator);
+ }
+
+ [Test]
+ public void CompareToThrowsOnNullParameters()
+ {
+ // Act
+ // ReSharper disable ReturnValueOfPureMethodIsNotUsed
+ // ReSharper disable UnusedVariable
+ TestDelegate actGenericCompareTo = () => new KspVersion().CompareTo(null);
+ TestDelegate actNonGenericCompareTo = () => new KspVersion().CompareTo((object)null);
+ TestDelegate lessThanOperatorNullLeft = () => { var _ = null < new KspVersion(); };
+ TestDelegate lessThanOperatorNullRight = () => { var _ = new KspVersion() < null; };
+ TestDelegate lessThanOrEqualOperatorNullLeft = () => { var _ = null <= new KspVersion(); };
+ TestDelegate lessThanOrEqualOperatorNullRight = () => { var _ = new KspVersion() <= null; };
+ TestDelegate greaterThanOperatorNullLeft = () => { var _ = null > new KspVersion(); };
+ TestDelegate greaterThanOperatorNullRight = () => { var _ = new KspVersion() > null; };
+ TestDelegate greaterThanOrEqualOperatorNullLeft = () => { var _ = null >= new KspVersion(); };
+ TestDelegate greaterThanOrEqualOperatorNullRight = () => { var _ = new KspVersion() >= null; };
+ // ReSharper restore UnusedVariable
+ // ReSharper restore ReturnValueOfPureMethodIsNotUsed
+
+ // Assert
+ Assert.That(actGenericCompareTo, Throws.Exception);
+ Assert.That(actNonGenericCompareTo, Throws.Exception);
+ Assert.That(lessThanOperatorNullLeft, Throws.Exception);
+ Assert.That(lessThanOperatorNullRight, Throws.Exception);
+ Assert.That(lessThanOrEqualOperatorNullLeft, Throws.Exception);
+ Assert.That(lessThanOrEqualOperatorNullRight, Throws.Exception);
+ Assert.That(greaterThanOperatorNullLeft, Throws.Exception);
+ Assert.That(greaterThanOperatorNullRight, Throws.Exception);
+ Assert.That(greaterThanOrEqualOperatorNullLeft, Throws.Exception);
+ Assert.That(greaterThanOrEqualOperatorNullRight, Throws.Exception);
+ }
+
+ [Test]
+ public void NonGenericCompareToThrowsOnInvalidType()
+ {
+ // Act
+ // ReSharper disable once ReturnValueOfPureMethodIsNotUsed
+ TestDelegate act = () => new KspVersion().CompareTo(new object());
+
+ // Assert
+ Assert.That(act, Throws.ArgumentException);
+ }
+
+ [Test]
+ public void GetHashCodeDoesNotThrow(
+ [Random(0, int.MaxValue, 1)]int major,
+ [Random(0, int.MaxValue, 1)]int minor,
+ [Random(0, int.MaxValue, 1)]int patch,
+ [Random(0, int.MaxValue, 1)]int build
+ )
+ {
+ // Act
+ // ReSharper disable once ReturnValueOfPureMethodIsNotUsed
+ TestDelegate act = () => new KspVersion(major, minor, patch, build).GetHashCode();
+
+ // Assert
+ Assert.That(act, Throws.Nothing);
+ }
+ }
+}
diff --git a/Tests/Data/TestData.cs b/Tests/Data/TestData.cs
index 95c18a726b..c05dd8d28a 100644
--- a/Tests/Data/TestData.cs
+++ b/Tests/Data/TestData.cs
@@ -3,6 +3,7 @@
using System.Globalization;
using System.IO;
using CKAN;
+using CKAN.Versioning;
using Version = CKAN.Version;
namespace Tests.Data
@@ -470,7 +471,7 @@ public RandomModuleGenerator(Random generator)
}
public CkanModule GeneratorRandomModule(
- KSPVersion ksp_version = null,
+ KspVersion ksp_version = null,
List conflicts = null,
List depends = null,
List sugests = null,
@@ -484,10 +485,10 @@ public CkanModule GeneratorRandomModule(
@abstract = Generator.Next().ToString(CultureInfo.InvariantCulture),
identifier = identifier??Generator.Next().ToString(CultureInfo.InvariantCulture),
spec_version = new Version(1.ToString(CultureInfo.InvariantCulture)),
- ksp_version = ksp_version ?? new KSPVersion("0." + Generator.Next()),
+ ksp_version = ksp_version ?? KspVersion.Parse("0." + Generator.Next()),
version = version ?? new Version(Generator.Next().ToString(CultureInfo.InvariantCulture))
};
- mod.ksp_version_max = mod.ksp_version_min = new KSPVersion(null);
+ mod.ksp_version_max = mod.ksp_version_min = null;
mod.conflicts = conflicts;
mod.depends = depends;
mod.suggests = sugests;
diff --git a/Tests/NetKAN/AVC.cs b/Tests/NetKAN/AVC.cs
index eda5537b0e..0b4833b443 100644
--- a/Tests/NetKAN/AVC.cs
+++ b/Tests/NetKAN/AVC.cs
@@ -3,6 +3,7 @@
using CKAN;
using CKAN.NetKAN;
using CKAN.NetKAN.Sources.Avc;
+using CKAN.Versioning;
using Newtonsoft.Json;
using NUnit.Framework;
using Tests.Data;
@@ -42,8 +43,8 @@ public void WildcardMajor_OutputsAnyVersion()
var converter = new JsonAvcToKspVersion();
string json = @"{""MAJOR"":-1, ""MINOR"":-1, ""PATCH"":-1}";
var reader = new JsonTextReader(new StringReader(json));
- var result = (KSPVersion) converter.ReadJson(reader, null, null, null);
- Assert.That(result.IsAny());
+ var result = (KspVersion) converter.ReadJson(reader, null, null, null);
+ Assert.That(!result.IsMajorDefined);
}
[Test]
@@ -52,8 +53,8 @@ public void WildcardMinor_VersionOnlyHasMajor()
var converter = new JsonAvcToKspVersion();
string json = @"{""MAJOR"":1, ""MINOR"":-1, ""PATCH"":-1}";
var reader = new JsonTextReader(new StringReader(json));
- var result = (KSPVersion) converter.ReadJson(reader, null, null, null);
- Assert.That(result, Is.EqualTo(new KSPVersion("1")));
+ var result = (KspVersion) converter.ReadJson(reader, null, null, null);
+ Assert.That(result, Is.EqualTo(KspVersion.Parse("1")));
}
[Test]
public void WildcardPatch_VersionOnlyHasMajorMinor()
@@ -61,8 +62,8 @@ public void WildcardPatch_VersionOnlyHasMajorMinor()
var converter = new JsonAvcToKspVersion();
string json = @"{""MAJOR"":1, ""MINOR"":5, ""PATCH"":-1}";
var reader = new JsonTextReader(new StringReader(json));
- var result = (KSPVersion)converter.ReadJson(reader, null, null, null);
- Assert.That(result, Is.EqualTo(new KSPVersion("1.5")));
+ var result = (KspVersion)converter.ReadJson(reader, null, null, null);
+ Assert.That(result, Is.EqualTo(KspVersion.Parse("1.5")));
}
}
}
\ No newline at end of file
diff --git a/Tests/NetKAN/Services/ModuleServiceTests.cs b/Tests/NetKAN/Services/ModuleServiceTests.cs
index aebe82349f..49524341dc 100644
--- a/Tests/NetKAN/Services/ModuleServiceTests.cs
+++ b/Tests/NetKAN/Services/ModuleServiceTests.cs
@@ -1,5 +1,6 @@
using CKAN;
using CKAN.NetKAN.Services;
+using CKAN.Versioning;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using Tests.Data;
@@ -86,13 +87,13 @@ public void GetsInternalAvcCorrectly()
Assert.That(result.version, Is.EqualTo(new Version("1.1.0.0")),
"ModuleService should get correct version from the internal AVC file."
);
- Assert.That(result.ksp_version, Is.EqualTo(new KSPVersion("0.24.2")),
+ Assert.That(result.ksp_version, Is.EqualTo(KspVersion.Parse("0.24.2")),
"ModuleService should get correct ksp_version from the internal AVC file."
);
- Assert.That(result.ksp_version_min, Is.EqualTo(new KSPVersion("0.24.0")),
+ Assert.That(result.ksp_version_min, Is.EqualTo(KspVersion.Parse("0.24.0")),
"ModuleService should get correct ksp_version_min from the internal AVC file."
);
- Assert.That(result.ksp_version_max, Is.EqualTo(new KSPVersion("0.24.2")),
+ Assert.That(result.ksp_version_max, Is.EqualTo(KspVersion.Parse("0.24.2")),
"ModuleService should get correct ksp_version_max from the internal AVC file."
);
}
diff --git a/Tests/NetKAN/Transformers/AvcTransformerTests.cs b/Tests/NetKAN/Transformers/AvcTransformerTests.cs
index 12c0a5c0b6..ba1feddd56 100644
--- a/Tests/NetKAN/Transformers/AvcTransformerTests.cs
+++ b/Tests/NetKAN/Transformers/AvcTransformerTests.cs
@@ -3,6 +3,7 @@
using CKAN.NetKAN.Services;
using CKAN.NetKAN.Sources.Avc;
using CKAN.NetKAN.Transformers;
+using CKAN.Versioning;
using Moq;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
@@ -19,7 +20,7 @@ public void AddsMissingVersionInfo()
var avcVersion = new AvcVersion
{
version = new Version("1.0.0"),
- ksp_version = new KSPVersion("1.0.4")
+ ksp_version = KspVersion.Parse("1.0.4")
};
var mHttp = new Mock();
@@ -55,9 +56,9 @@ public void PreferentiallyAddsRangedKspVersionInfo()
// Arrange
var avcVersion = new AvcVersion
{
- ksp_version = new KSPVersion("1.0.4"),
- ksp_version_min = new KSPVersion("0.90"),
- ksp_version_max = new KSPVersion("1.0.3")
+ ksp_version = KspVersion.Parse("1.0.4"),
+ ksp_version_min = KspVersion.Parse("0.90"),
+ ksp_version_max = KspVersion.Parse("1.0.3")
};
var mHttp = new Mock();
@@ -175,13 +176,13 @@ public void CorrectlyCalculatesKspVersionInfo(
var avcVersion = new AvcVersion();
if (!string.IsNullOrWhiteSpace(avcKsp))
- avcVersion.ksp_version = new KSPVersion(avcKsp);
+ avcVersion.ksp_version = KspVersion.Parse(avcKsp);
if (!string.IsNullOrWhiteSpace(avcKspMin))
- avcVersion.ksp_version_min = new KSPVersion(avcKspMin);
+ avcVersion.ksp_version_min = KspVersion.Parse(avcKspMin);
if (!string.IsNullOrWhiteSpace(avcKspMax))
- avcVersion.ksp_version_max = new KSPVersion(avcKspMax);
+ avcVersion.ksp_version_max = KspVersion.Parse(avcKspMax);
var mHttp = new Mock();
var mModuleService = new Mock();
diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj
index 3eec213fd1..a24347e39a 100644
--- a/Tests/Tests.csproj
+++ b/Tests/Tests.csproj
@@ -20,6 +20,7 @@
prompt
4
false
+ 5
pdbonly
@@ -125,12 +126,13 @@
-
+
+
@@ -202,4 +204,4 @@
-
+
\ No newline at end of file