Beispiel #1
0
        public void FakeInstance_ValidArgumentsWithDLCs_ManagerHasValidInstance()
        {
            string     name      = "testname";
            KspVersion mhVersion = KspVersion.Parse("1.1.0");
            KspVersion bgVersion = KspVersion.Parse("1.0.0");
            string     tempdir   = TestData.NewTempDir();
            KspVersion version   = KspVersion.Parse("1.7.1");

            Dictionary <CKAN.DLC.IDlcDetector, KspVersion> dlcs = new Dictionary <CKAN.DLC.IDlcDetector, KspVersion>()
            {
                { new CKAN.DLC.MakingHistoryDlcDetector(), mhVersion },
                { new CKAN.DLC.BreakingGroundDlcDetector(), bgVersion }
            };

            manager.FakeInstance(name, tempdir, version, dlcs);
            CKAN.KSP newKSP = new CKAN.KSP(tempdir, name, new NullUser());
            CKAN.DLC.MakingHistoryDlcDetector  mhDetector = new CKAN.DLC.MakingHistoryDlcDetector();
            CKAN.DLC.BreakingGroundDlcDetector bgDetector = new CKAN.DLC.BreakingGroundDlcDetector();

            Assert.IsTrue(manager.HasInstance(name));
            Assert.IsTrue(mhDetector.IsInstalled(newKSP, out string _, out UnmanagedModuleVersion detectedMhVersion));
            Assert.IsTrue(bgDetector.IsInstalled(newKSP, out string _, out UnmanagedModuleVersion detectedBgVersion));
            Assert.IsTrue(detectedMhVersion == new UnmanagedModuleVersion(mhVersion.ToString()));
            Assert.IsTrue(detectedBgVersion == new UnmanagedModuleVersion(bgVersion.ToString()));

            // Tidy up.
            CKAN.RegistryManager.Instance(newKSP).ReleaseLock();
            System.IO.Directory.Delete(tempdir, true);
        }
Beispiel #2
0
        /// <summary>
        /// Returns a human readable string indicating the highest compatible
        /// version of KSP this module will run with. (Eg: 1.0.2,
        /// "All versions", etc).
        ///
        /// This is for *human consumption only*, as the strings may change in the
        /// future as we support additional locales.
        /// </summary>
        public string HighestCompatibleKSP()
        {
            KspVersion v = LatestCompatibleKSP();

            if (v.IsAny)
            {
                return("All versions");
            }
            else
            {
                return(v.ToString());
            }
        }
Beispiel #3
0
        /// <summary>
        /// Returns a human readable string indicating the highest compatible
        /// version of KSP this module will run with. (Eg: 1.0.2, 1.0.2+,
        /// "All version", etc).
        ///
        /// This is for *human consumption only*, as the strings may change in the
        /// future as we support additional locales.
        /// </summary>
        public string HighestCompatibleKSP()
        {
            // Find the highest compatible KSP version
            if (ksp_version_max != null)
            {
                return(ksp_version_max.ToString());
            }
            else if (ksp_version != null)
            {
                return(ksp_version.ToString());
            }
            else if (ksp_version_min != null)
            {
                return(ksp_version_min + "+");
            }

            return("All versions");
        }
Beispiel #4
0
        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());
        }
Beispiel #5
0
        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());
        }
Beispiel #6
0
        private Metadata TransformOne(JObject json, CurseMod curseMod, CurseFile latestVersion)
        {
            Log.InfoFormat("Found Curse mod: {0} {1}", curseMod.GetName(), latestVersion.GetFileVersion());

            // Only pre-fill version info if there's none already. GH #199
            if (json["ksp_version_min"] == null && json["ksp_version_max"] == null && json["ksp_version"] == null)
            {
                KspVersion minVer = latestVersion.versions.Min();
                KspVersion maxVer = latestVersion.versions.Max();
                if (minVer == maxVer)
                {
                    Log.DebugFormat("Writing ksp_version from Curse: {0}", latestVersion.version);
                    json["ksp_version"] = latestVersion.version.ToString();
                }
                else
                {
                    Log.DebugFormat("Writing ksp_version_min,_max from Curse: {0}, {1}", minVer, maxVer);
                    json["ksp_version_min"] = minVer.ToString();
                    json["ksp_version_max"] = maxVer.ToString();
                }
            }

            var useDownloadNameVersion = false;
            var useFilenameVersion     = false;
            var useCurseIdVersion      = false;

            var curseMetadata = (JObject)json["x_netkan_curse"];

            if (curseMetadata != null)
            {
                var useDownloadNameVersionMetadata = (bool?)curseMetadata["use_download_name_version"];
                if (useDownloadNameVersionMetadata != null)
                {
                    useDownloadNameVersion = useDownloadNameVersionMetadata.Value;
                }

                var useFilenameVersionMetadata = (bool?)curseMetadata["use_filename_version"];
                if (useFilenameVersionMetadata != null)
                {
                    useFilenameVersion = useFilenameVersionMetadata.Value;
                }

                var useCurseIdVersionMetadata = (bool?)curseMetadata["use_curse_id_version"];
                if (useCurseIdVersionMetadata != null)
                {
                    useCurseIdVersion = useCurseIdVersionMetadata.Value;
                }

                if ((useDownloadNameVersion ? 1 : 0) + (useFilenameVersion ? 1 : 0) + (useCurseIdVersion ? 1 : 0) > 1)
                {
                    throw new Kraken("Conflicting version options set in x_netkan_curse");
                }
            }

            json.SafeAdd("name", curseMod.GetName());
            json.SafeAdd("abstract", curseMod.description);

            if (useDownloadNameVersion)
            {
                json.SafeAdd("version", latestVersion.name);
            }
            else if (useFilenameVersion)
            {
                json.SafeAdd("version", latestVersion.GetFilename());
            }
            else if (useCurseIdVersion)
            {
                json.SafeAdd("version", latestVersion.GetCurseIdVersion());
            }
            else
            {
                json.SafeAdd("version", latestVersion.GetFileVersion());
            }

            json.SafeAdd("author", JToken.FromObject(curseMod.authors));
            json.SafeAdd("download", Regex.Replace(latestVersion.GetDownloadUrl(), " ", "%20"));

            // Curse provides users with the following default selection of licenses. Let's convert them to CKAN
            // compatible license strings if possible.
            //
            // "Academic Free License v3.0"                               - Becomes "AFL-3.0"
            // "Ace3 Style BSD"                                           - Becomes "restricted"
            // "All Rights Reserved"                                      - Becomes "restricted"
            // "Apache License version 2.0"                               - Becomes "Apache-2.0"
            // "Apple Public Source License version 2.0 (APSL)"           - Becomes "APSL-2.0"
            // "BSD License"                                              - Becomes "BSD-3-clause"
            // "Common Development and Distribution License (CDDL) "      - Becomes "CDDL"
            // "GNU Affero General Public License version 3 (AGPLv3)"     - Becomes "AGPL-3.0"
            // "GNU General Public License version 2 (GPLv2)"             - Becomes "GPL-2.0"
            // "GNU General Public License version 3 (GPLv3)"             - Becomes "GPL-3.0"
            // "GNU Lesser General Public License version 2.1 (LGPLv2.1)" - Becomes "LGPL-2.1"
            // "GNU Lesser General Public License version 3 (LGPLv3)"     - Becomes "LGPL-3.0"
            // "ISC License (ISCL)"                                       - Becomes "ISC"
            // "Microsoft Public License (Ms-PL)"                         - Becomes "Ms-PL"
            // "Microsoft Reciprocal License (Ms-RL)"                     - Becomes "Ms-RL"
            // "MIT License"                                              - Becomes "MIT"
            // "Mozilla Public License 1.0 (MPL)"                         - Becomes "MPL-1.0"
            // "Mozilla Public License 1.1 (MPL 1.1)"                     - Becomes "MPL-1.1"
            // "Public Domain"                                            - Becomes "public-domain"
            // "WTFPL"                                                    - Becomes "WTFPL"
            // "zlib/libpng License"                                      - Becomes "Zlib"
            // "Custom License"                                           - Becomes "unknown"

            var curseLicense = curseMod.license.Trim();

            switch (curseLicense)
            {
            case "Academic Free License v3.0":
                json.SafeAdd("license", "AFL-3.0");
                break;

            case "Ace3 Style BSD":
                json.SafeAdd("license", "restricted");
                break;

            case "All Rights Reserved":
                json.SafeAdd("license", "restricted");
                break;

            case "Apache License version 2.0":
                json.SafeAdd("license", "Apache-2.0");
                break;

            case "Apple Public Source License version 2.0 (APSL)":
                json.SafeAdd("license", "APSL-2.0");
                break;

            case "BSD License":
                json.SafeAdd("license", "BSD-3-clause");
                break;

            case "Common Development and Distribution License (CDDL) ":
                json.SafeAdd("license", "CDDL");
                break;

            case "GNU Affero General Public License version 3 (AGPLv3)":
                json.SafeAdd("license", "AGPL-3.0");
                break;

            case "GNU General Public License version 2 (GPLv2)":
                json.SafeAdd("license", "GPL-2.0");
                break;

            case "GNU General Public License version 3 (GPLv3)":
                json.SafeAdd("license", "GPL-3.0");
                break;

            case "GNU Lesser General Public License version 2.1 (LGPLv2.1)":
                json.SafeAdd("license", "LGPL-2.1");
                break;

            case "GNU Lesser General Public License version 3 (LGPLv3)":
                json.SafeAdd("license", "LGPL-3.0");
                break;

            case "ISC License (ISCL)":
                json.SafeAdd("license", "ISC");
                break;

            case "Microsoft Public License (Ms-PL)":
                json.SafeAdd("license", "Ms-PL");
                break;

            case "Microsoft Reciprocal License (Ms-RL)":
                json.SafeAdd("license", "Ms-RL");
                break;

            case "MIT License":
                json.SafeAdd("license", "MIT");
                break;

            case "Mozilla Public License 1.0 (MPL)":
                json.SafeAdd("license", "MPL-1.0");
                break;

            case "Mozilla Public License 1.1 (MPL 1.1)":
                json.SafeAdd("license", "MPL-1.1");
                break;

            case "Public Domain":
                json.SafeAdd("license", "public-domain");
                break;

            case "WTFPL":
                json.SafeAdd("license", "WTFPL");
                break;

            case "zlib/libpng License":
                json.SafeAdd("license", "Zlib");
                break;

            default:
                json.SafeAdd("license", "unknown");
                break;
            }

            // Make sure resources exist.
            if (json["resources"] == null)
            {
                json["resources"] = new JObject();
            }

            var resourcesJson = (JObject)json["resources"];

            //resourcesJson.SafeAdd("homepage", Normalize(curseMod.website));
            //resourcesJson.SafeAdd("repository", Normalize(curseMod.source_code));
            resourcesJson.SafeAdd("curse", curseMod.GetProjectUrl());

            if (curseMod.thumbnail != null)
            {
                resourcesJson.SafeAdd("x_screenshot", Normalize(new Uri(curseMod.thumbnail)));
            }

            Log.DebugFormat("Transformed metadata:{0}{1}", Environment.NewLine, json);

            return(new Metadata(json));
        }
Beispiel #7
0
        /// <summary>
        /// Create a new fake KSP instance
        /// </summary>
        /// <param name="newName">The name for the new instance.</param>
        /// <param name="newPath">The loaction of the new instance.</param>
        /// <param name="version">The version of the new instance. Should have a build number.</param>
        /// <param name="dlcs">The IDlcDetector implementations for the DLCs that should be faked and the requested dlc version as a dictionary.</param>
        public void FakeInstance(string newName, string newPath, KspVersion version, Dictionary<DLC.IDlcDetector, KspVersion> dlcs = null)
        {
            TxFileManager fileMgr = new TxFileManager();
            using (TransactionScope transaction = CkanTransaction.CreateTransactionScope())
            {
                if (HasInstance(newName))
                {
                    throw new InstanceNameTakenKraken(newName);
                }


                if (!version.InBuildMap())
                {
                    throw new BadKSPVersionKraken(String.Format("The specified KSP version is not a known version: {0}", version.ToString()));
                }
                if (Directory.Exists(newPath) && (Directory.GetFiles(newPath).Length != 0 || Directory.GetDirectories(newPath).Length != 0))
                {
                    throw new BadInstallLocationKraken("The specified folder already exists and is not empty.");
                }

                try
                {
                    log.DebugFormat("Creating folder structure and text files at {0} for KSP version {1}", Path.GetFullPath(newPath), version.ToString());

                    // Create a KSP root directory, containing a GameData folder, a buildID.txt/buildID64.txt and a readme.txt
                    fileMgr.CreateDirectory(newPath);
                    fileMgr.CreateDirectory(Path.Combine(newPath, "GameData"));
                    fileMgr.CreateDirectory(Path.Combine(newPath, "Ships"));
                    fileMgr.CreateDirectory(Path.Combine(newPath, "Ships", "VAB"));
                    fileMgr.CreateDirectory(Path.Combine(newPath, "Ships", "SPH"));
                    fileMgr.CreateDirectory(Path.Combine(newPath, "Ships", "@thumbs"));
                    fileMgr.CreateDirectory(Path.Combine(newPath, "Ships", "@thumbs", "VAB"));
                    fileMgr.CreateDirectory(Path.Combine(newPath, "Ships", "@thumbs", "SPH"));

                    // Don't write the buildID.txts if we have no build, otherwise it would be -1.
                    if (version.IsBuildDefined)
                    {
                        fileMgr.WriteAllText(Path.Combine(newPath, "buildID.txt"), String.Format("build id = {0}", version.Build));
                        fileMgr.WriteAllText(Path.Combine(newPath, "buildID64.txt"), String.Format("build id = {0}", version.Build));
                    }

                    // Create the readme.txt WITHOUT build number.
                    fileMgr.WriteAllText(Path.Combine(newPath, "readme.txt"), String.Format("Version {0}", new KspVersion(version.Major, version.Minor, version.Patch).ToString()));

                    // Create the needed folder structure and the readme.txt for DLCs that should be simulated.
                    if (dlcs != null)
                    {
                        foreach (KeyValuePair<DLC.IDlcDetector, KspVersion> dlc in dlcs)
                        {
                            DLC.IDlcDetector dlcDetector = dlc.Key;
                            KspVersion dlcVersion = dlc.Value;

                            if (!dlcDetector.AllowedOnBaseVersion(version))
                                throw new WrongKSPVersionKraken(
                                    version,
                                    String.Format("KSP version {0} or above is needed for {1} DLC.",
                                        dlcDetector.ReleaseGameVersion,
                                        dlcDetector.IdentifierBaseName
                                ));

                            string dlcDir = Path.Combine(newPath, dlcDetector.InstallPath());
                            fileMgr.CreateDirectory(dlcDir);
                            fileMgr.WriteAllText(
                                Path.Combine(dlcDir, "readme.txt"),
                                String.Format("Version {0}", dlcVersion));
                        }
                    }

                    // Add the new instance to the registry
                    KSP new_instance = new KSP(newPath, newName, User, false);
                    AddInstance(new_instance);
                }
                // Thrown by AddInstance() if created instance is not valid.
                // Thrown f.e. if a write operation didn't complete for unknown reasons.
                catch (NotKSPDirKraken kraken)
                {
                    throw kraken;
                }
                transaction.Complete();
            }
        }
Beispiel #8
0
        /// <summary>
        /// Create a new fake KSP instance
        /// </summary>
        /// <param name="new_name">The name for the new instance.</param>
        /// <param name="new_path">The loaction of the new instance.</param>
        /// <param name="version">The version of the new instance. Should have a build number.</param>
        /// <param name="dlcVersion">The version of the DLC. Null if DLC should be faked.</param>
        public void FakeInstance(string new_name, string new_path, KspVersion version, string dlcVersion = null)
        {
            if (!version.InBuildMap())
            {
                throw new IncorrectKSPVersionKraken(String.Format("The specified KSP version is not a known version: {0}", version.ToString()));
            }
            if (Directory.Exists(new_path) && (Directory.GetFiles(new_path).Length != 0 || Directory.GetDirectories(new_path).Length != 0))
            {
                throw new BadInstallLocationKraken("The specified folder already exists and is not empty.");
            }

            try
            {
                log.DebugFormat("Creating folder structure and text files at {0} for KSP version {1}", Path.GetFullPath(new_path), version.ToString());

                // Create a KSP root directory, containing a GameData folder, a buildID.txt/buildID64.txt and a readme.txt
                Directory.CreateDirectory(new_path);
                Directory.CreateDirectory(Path.Combine(new_path, "GameData"));
                Directory.CreateDirectory(Path.Combine(new_path, "Ships"));
                Directory.CreateDirectory(Path.Combine(new_path, "Ships", "VAB"));
                Directory.CreateDirectory(Path.Combine(new_path, "Ships", "SPH"));
                Directory.CreateDirectory(Path.Combine(new_path, "Ships", "@thumbs"));
                Directory.CreateDirectory(Path.Combine(new_path, "Ships", "@thumbs", "VAB"));
                Directory.CreateDirectory(Path.Combine(new_path, "Ships", "@thumbs", "SPH"));

                // Don't write the buildID.txts if we have no build, otherwise it would be -1.
                if (version.IsBuildDefined)
                {
                    File.WriteAllText(Path.Combine(new_path, "buildID.txt"), String.Format("build id = {0}", version.Build));
                    File.WriteAllText(Path.Combine(new_path, "buildID64.txt"), String.Format("build id = {0}", version.Build));
                }

                // Create the readme.txt WITHOUT build number.
                File.WriteAllText(Path.Combine(new_path, "readme.txt"), String.Format("Version {0}", new KspVersion(version.Major, version.Minor, version.Patch).ToString()));

                // If a installed DLC should be simulated, we create the needed folder structure and the readme.txt
                if (!String.IsNullOrEmpty(dlcVersion) && version.CompareTo(new KspVersion(1, 4, 0)) >= 0)
                {
                    Directory.CreateDirectory(Path.Combine(new_path, "GameData", "SquadExpansion", "MakingHistory"));
                    File.WriteAllText(
                        Path.Combine(new_path, "GameData", "SquadExpansion", "MakingHistory", "readme.txt"),
                        String.Format("Version {0}", dlcVersion));
                }

                // Add the new instance to the registry
                KSP new_instance = new KSP(new_path, new_name, User);
                AddInstance(new_instance);
            }
            // Thrown by AddInstance() if created instance is not valid.
            // Thrown f.e. if a write operation didn't complete for unknown reasons.
            catch (NotKSPDirKraken kraken)
            {
                throw kraken;
            }
        }
Beispiel #9
0
        public GUIMod(CkanModule mod, IRegistryQuerier registry, KspVersionCriteria current_ksp_version, bool incompatible = false)
        {
            IsCKAN = mod is CkanModule;
            //Currently anything which could alter these causes a full reload of the modlist
            // If this is ever changed these could be moved into the properties
            Mod              = mod;
            IsInstalled      = registry.IsInstalled(mod.identifier, false);
            IsInstallChecked = IsInstalled;
            HasUpdate        = registry.HasUpdate(mod.identifier, current_ksp_version);
            IsIncompatible   = incompatible || !mod.IsCompatibleKSP(current_ksp_version);
            IsAutodetected   = registry.IsAutodetected(mod.identifier);
            Authors          = mod.author == null ? "N/A" : String.Join(",", mod.author);

            var           installed_version = registry.InstalledVersion(mod.identifier);
            ModuleVersion latest_version    = null;
            var           ksp_version       = mod.ksp_version;

            try
            {
                var latest_available = registry.LatestAvailable(mod.identifier, current_ksp_version);
                if (latest_available != null)
                {
                    latest_version = latest_available.version;
                }
            }
            catch (ModuleNotFoundKraken)
            {
                latest_version = installed_version;
            }

            InstalledVersion = installed_version != null?installed_version.ToString() : "-";

            // Let's try to find the compatibility for this mod. If it's not in the registry at
            // all (because it's a DarkKAN mod) then this might fail.

            CkanModule latest_available_for_any_ksp = null;

            try
            {
                latest_available_for_any_ksp = registry.LatestAvailable(mod.identifier, null);
            }
            catch
            {
                // If we can't find the mod in the CKAN, but we've a CkanModule installed, then
                // use that.
                if (IsCKAN)
                {
                    latest_available_for_any_ksp = (CkanModule)mod;
                }
            }

            // If there's known information for this mod in any form, calculate the highest compatible
            // KSP.
            if (latest_available_for_any_ksp != null)
            {
                if (!VersionMaxWasGenerated)
                {
                    VersionMaxWasGenerated = true;
                    List <KspVersion> versions = new KspBuildMap(new Win32Registry()).KnownVersions;  // should be sorted

                    VersionsMax     = new Dictionary <string, KspVersion>();
                    VersionsMax[""] = versions.Last();

                    foreach (var v in versions)
                    {
                        VersionsMax[v.Major.ToString()]      = v;   // add or replace
                        VersionsMax[v.Major + "." + v.Minor] = v;
                    }
                }

                const int Undefined = -1;

                KspVersion ksp_ver = registry.LatestCompatibleKSP(mod.identifier);
                string     ver = ksp_ver?.ToString();
                int        major = ksp_ver.Major, minor = ksp_ver.Minor, patch = ksp_ver.Patch;
                KspVersion value;

                if (major == Undefined
                    //|| (major >= UptoNines(VersionsMax[""].Major))       // 9.99.99
                    || (major > VersionsMax[""].Major) ||                                                                                       // 2.0.0
                    (major == VersionsMax[""].Major && VersionsMax.TryGetValue(major.ToString(), out value) && minor >= UptoNines(value.Minor)) // 1.99.99 ?
                    )
                {
                    KSPCompatibility = "any";
                }

                else if (minor != Undefined &&
                         VersionsMax.TryGetValue(major + "." + minor, out value) &&
                         (patch == Undefined || patch >= UptoNines(value.Patch))
                         )
                {
                    KSPCompatibility = major + "." + minor + "." + UptoNines(value.Patch);
                }

                else
                {
                    KSPCompatibility = ver;
                }

                // KSPCompatibility += " | " + major + "." + minor + "." + patch;   // for testing

                // If the mod we have installed is *not* the mod we have installed, or we don't know
                // what we have installed, indicate that an upgrade would be needed.
                if (installed_version == null || !latest_available_for_any_ksp.version.IsEqualTo(installed_version))
                {
                    KSPCompatibilityLong = string.Format("{0} (using mod version {1})",
                                                         KSPCompatibility, latest_available_for_any_ksp.version);
                    //    ver, latest_available_for_any_ksp.version);   //  true values in the right tab
                }
                else
                {
                    KSPCompatibilityLong = KSPCompatibility;
                    // KSPCompatibilityLong = ver;   //  true values in the right tab
                }
            }
            else
            {
                // No idea what this mod is, sorry!
                KSPCompatibility = KSPCompatibilityLong = "unknown";
            }

            if (latest_version != null)
            {
                LatestVersion = latest_version.ToString();
            }
            else if (latest_available_for_any_ksp != null)
            {
                LatestVersion = latest_available_for_any_ksp.version.ToString();
            }
            else
            {
                LatestVersion = "-";
            }

            KSPversion = ksp_version != null?ksp_version.ToString() : "-";

            Abstract = mod.@abstract;

            // If we have a homepage provided, use that; otherwise use the spacedock page, curse page or the github repo so that users have somewhere to get more info than just the abstract.

            Homepage = "N/A";
            if (mod.resources != null)
            {
                if (mod.resources.homepage != null)
                {
                    Homepage = mod.resources.homepage.ToString();
                }
                else if (mod.resources.spacedock != null)
                {
                    Homepage = mod.resources.spacedock.ToString();
                }
                else if (mod.resources.curse != null)
                {
                    Homepage = mod.resources.curse.ToString();
                }
                else if (mod.resources.repository != null)
                {
                    Homepage = mod.resources.repository.ToString();
                }
            }

            Identifier = mod.identifier;

            DownloadSize = (mod.download_size == 0)
                ? "N/A"
                : CkanModule.FmtSize(mod.download_size);

            Abbrevation = new string(mod.name.Split(' ').
                                     Where(s => s.Length > 0).Select(s => s[0]).ToArray());

            UpdateIsCached();
        }