/// <summary>
 /// Gets the content of the site of the passed URL and parses it for ModInfos.
 /// </summary>
 /// <param name="url">The URL of the site to parse the ModInfos from.</param>
 /// <returns>The ModInfos parsed from the site of the passed URL.</returns>
 public ModInfo GetModInfo(string url)
 {
     var modInfo = new ModInfo
     {
         SiteHandlerName = Name,
         ModURL = ReduceToPlainUrl(url)
     };
     ParseSite(ref modInfo);
     return modInfo;
 }
Example #2
0
 /// <summary>
 /// Gets the content of the site of the passed URL and parses it for ModInfos.
 /// </summary>
 /// <param name="url">The URL to the KSP forum site to parse the ModInfos from.</param>
 /// <returns>The ModInfos parsed from the site of the passed URL.</returns>
 public static ModInfo GetModInfo(string url)
 {
     ModInfo modInfo = new ModInfo();
     modInfo.SiteHandlerName = Name;
     modInfo.ModURL = url;
     if (ParseSite(Www.Load(url), ref modInfo))
         return modInfo;
     else
         return null;
 }
 /// <summary>
 /// Gets the content of the site of the passed URL and parses it for ModInfos.
 /// </summary>
 /// <param name="url">The URL of the site to parse the ModInfos from.</param>
 /// <returns>The ModInfos parsed from the site of the passed URL.</returns>
 public ModInfo GetModInfo(string url)
 {
     var parts = GetUrlParts(url);
     var modInfo = new ModInfo
     {
         SiteHandlerName = Name,
         ModURL = ReduceToPlainUrl(url),
         Name = parts[3],
         Author = parts[2]
     };
     ParseSite(ref modInfo);
     return modInfo;
 }
        /// <summary>
        /// Gets the content of the site of the passed URL and parses it for ModInfos.
        /// </summary>
        /// <param name="url">The URL of the site to parse the ModInfos from.</param>
        /// <returns>The ModInfos parsed from the site of the passed URL.</returns>
        public ModInfo GetModInfo(string url)
        {
            ModInfo modInfo = new ModInfo
            {
                SiteHandlerName = Name, 
                ModURL = ReduceToPlainUrl(url)
            };

            if (ParseSite(url, ref modInfo))
                return modInfo;

            return null;
        }
        /// <summary>
        /// Checks if updates are available for the passed mod.
        /// </summary>
        /// <param name="modInfo">The ModInfos of the mod to check for updates.</param>
        /// <param name="newModInfo">A reference to an empty ModInfo to write the updated ModInfos to.</param>
        /// <returns>True if there is an update, otherwise false.</returns>
        public bool CheckForUpdates(ModInfo modInfo, ref ModInfo newModInfo)
        {
            newModInfo = GetModInfo(modInfo.ModURL);

            if (string.IsNullOrEmpty(modInfo.ChangeDate) && !string.IsNullOrEmpty(newModInfo.ChangeDate))
                return true;
            else if (!string.IsNullOrEmpty(modInfo.ChangeDate) && !string.IsNullOrEmpty(newModInfo.ChangeDate))
                return modInfo.ChangeDateAsDateTime < newModInfo.ChangeDateAsDateTime;

            return false;
        }
        /// <summary>
        /// Adds a MOD to the TreeView.
        /// </summary>
        /// <param name="fileNames">Paths to the Zip-Files of the KSP mods.</param>
        /// <param name="showCollisionDialog">Flag to show/hide the collision dialog.</param>
        internal static void AddModsAsync(string[] fileNames, bool showCollisionDialog = true)
        {
            if (fileNames.Length > 0)
            {
                ModInfo[] modInfos = new ModInfo[fileNames.Length];
                for (int i = 0; i < fileNames.Length; ++i)
                    modInfos[i] = new ModInfo { LocalPath = fileNames[i], Name = Path.GetFileNameWithoutExtension(fileNames[i]) };

                AddModsAsync(modInfos, showCollisionDialog);
            }
            else
            {
                Messenger.AddError(Messages.MSG_ADD_MODS_FAILED_PARAM_EMPTY_FILENAMES);
            }
        }
        /// <summary>
        /// Creates a list of DownloadInfos from a GitHub release
        /// </summary>
        /// <param name="modInfo">The mod to generate the list from</param>
        /// <returns>A list of one or more DownloadInfos for the most recent release of the selected repository</returns>
        private static List<DownloadInfo> GetDownloadInfo(ModInfo modInfo)
        {
            var htmlDoc = new HtmlWeb().Load(GetPathToReleases(modInfo.ModURL));
            htmlDoc.OptionFixNestedTags = true;

            var releases = new List<DownloadInfo>();

            var nodesrel = htmlDoc.DocumentNode.SelectNodes("//*[@class='release label-latest']/div[2]/ul/li/a");

            var nodespre = htmlDoc.DocumentNode.SelectNodes("//*[@class='release label-prerelease'][1]/div[2]/ul/li/a");

            if (nodesrel != null)
            {
                foreach (var s in nodesrel)
                {
                    var url = "https://github.com" + s.Attributes["href"].Value;

                    if (!url.Contains("releases")) continue;

                    var dInfo = new DownloadInfo
                    {
                        DownloadURL = url,
                        Filename = GetUrlParts(url).Last(),
                        Name = Path.GetFileNameWithoutExtension(GetUrlParts(url).Last())
                    };

                    releases.Add(dInfo);
                }
            }

            if (nodespre != null)
            {
                foreach (var s in nodespre)
                {
                    var url = "https://github.com" + s.Attributes["href"].Value;

                    if (!url.Contains("releases")) continue;

                    var versionNode = htmlDoc.DocumentNode.SelectSingleNode("//*[@class='release label-prerelease']/div[1]/ul/li[1]/a/span[2]").InnerText;

                    var dInfo = new DownloadInfo
                    {
                        DownloadURL = url,
                        Filename = GetUrlParts(url).Last(),
                        Name = "Pre-release: " + versionNode + ": " + Path.GetFileNameWithoutExtension(GetUrlParts(url).Last())
                    };

                    releases.Add(dInfo);
                }
            }

            return releases;
        }
        /// <summary>
        /// Downloads the mod.
        /// </summary>
        /// <param name="modInfo">The infos of the mod. Must have at least ModURL and LocalPath</param>
        /// <param name="downloadProgressCallback">Callback function for download progress.</param>
        /// <returns>True if the mod was downloaded.</returns>
        public bool DownloadMod(ref ModInfo modInfo, DownloadProgressCallback downloadProgressCallback = null)
        {
            if (modInfo == null)
                return false;

            var downloadInfos = GetDownloadInfo(modInfo);
            DownloadInfo selected = null;

            // If any of the nodes came back as a prerelease, notify the user that there are pre-release nodes
            foreach (var d in downloadInfos)
            {
                if (!d.Name.Contains("Pre-release")) continue;

                var dlg = MessageBox.Show("This download contains a pre-release version. This version might not be stable.", Messages.MSG_TITLE_ATTENTION, MessageBoxButtons.OK);
                break;
            }

            if (downloadInfos.Count > 1)
            {
                // create new selection form if more than one download option found
                var dlg = new frmSelectDownload(downloadInfos);
                if (dlg.ShowDialog() == DialogResult.OK)
                {
                    selected = dlg.SelectedLink;
                }
            }
            else if (downloadInfos.Count == 1)
            {
                selected = downloadInfos.First();
            }
            else
            {
                string msg = string.Format(Messages.MSG_NO_BINARY_DOWNLOAD_FOUND_AT_0, modInfo.SiteHandlerName);
                MessageBox.Show(msg, Messages.MSG_TITLE_ERROR);
                Messenger.AddDebug(msg);
                return false;
            }

            if (selected != null)
            {
                string downloadUrl = selected.DownloadURL;
                modInfo.LocalPath = Path.Combine(OptionsController.DownloadPath, selected.Filename);
                Www.DownloadFile(downloadUrl, modInfo.LocalPath, downloadProgressCallback);
            }

            return File.Exists(modInfo.LocalPath);
        }
        /// <summary>
        /// Downloads the mod.
        /// </summary>
        /// <param name="modInfo">The infos of the mod. Must have at least ModURL and LocalPath</param>
        /// <param name="downloadProgressCallback">Callback function for download progress.</param>
        /// <returns>True if the mod was downloaded.</returns>
        public bool DownloadMod(ref ModInfo modInfo, DownloadProgressCallback downloadProgressCallback = null)
        {
            if (modInfo == null)
                return false;

            HtmlWeb web = new HtmlWeb();
            HtmlDocument htmlDoc = web.Load(modInfo.ModURL);
            htmlDoc.OptionFixNestedTags = true;

            // get filename from hover text
            HtmlNode fileNode = htmlDoc.DocumentNode.SelectSingleNode("//*[@id='content']/section[2]/div[4]/div[2]/ul/li[1]/div[2]/p/a");
            HtmlNode fileNode2 = htmlDoc.DocumentNode.SelectSingleNode("//*[@id='content']/section[2]/div[4]/div[2]/ul/li/div[2]/p/a/span");

            string downloadURL = GetDownloadURL(modInfo.ModURL);
            if (fileNode == null || (fileNode.InnerHtml.Contains("...") && fileNode2 == null))
            {
                modInfo.LocalPath = Www.DownloadFile2(downloadURL, OptionsController.DownloadPath, downloadProgressCallback);
                return !string.IsNullOrEmpty(modInfo.LocalPath) && File.Exists(modInfo.LocalPath);
            }

            string filename = string.Empty;
            if (fileNode.InnerHtml.Contains("..."))
                filename = fileNode2.Attributes["title"].Value; // Long filename was truncated
            else
                filename = fileNode.InnerHtml;

            modInfo.LocalPath = Path.Combine(OptionsController.DownloadPath, filename);

            Www.DownloadFile(downloadURL, modInfo.LocalPath, downloadProgressCallback);

            return File.Exists(modInfo.LocalPath);
        }
Example #10
0
 /// <summary>
 /// Creates a new instance of the ModNode class.
 /// </summary>
 public ModNode(ModInfo modInfo)
 {
     ModInfo = modInfo;
 }
 /// <summary>
 /// Checks if updates are available for the passed mod.
 /// </summary>
 /// <param name="modInfo">The ModInfos of the mod to check for updates.</param>
 /// <param name="newModInfo">A reference to an empty ModInfo to write the updated ModInfos to.</param>
 /// <returns>True if there is an update, otherwise false.</returns>
 public bool CheckForUpdates(ModInfo modInfo, ref ModInfo newModInfo)
 {
     newModInfo = GetModInfo(modInfo.ModURL);
     return (VersionComparer.CompareVersions(modInfo.Version, newModInfo.Version) == VersionComparer.Result.AisSmallerB);
 }
        /// <summary>
        /// Downloads the mod.
        /// </summary>
        /// <param name="modInfo">The infos of the mod. Must have at least ModURL and LocalPath</param>
        /// <param name="downloadProgressCallback">Callback function for download progress.</param>
        /// <returns>True if the mod was downloaded.</returns>
        public bool DownloadMod(ref ModInfo modInfo, DownloadProgressCallback downloadProgressCallback = null)
        {
            if (modInfo == null)
                return false;

            string downloadUrl = GetDownloadURL(modInfo);

            ////string siteContent = www.Load(GetFilesURL(modInfo.ModURL));
            ////string filename = GetFileName(siteContent);
            modInfo.LocalPath = Path.Combine(OptionsController.DownloadPath, GetDownloadName(modInfo));

            Www.DownloadFile(downloadUrl, modInfo.LocalPath, downloadProgressCallback);

            return File.Exists(modInfo.LocalPath);
        }
        /// <summary>
        /// Loads a mod's source page and extracts mod info from it
        /// </summary>
        /// <param name="modInfo">A mod to add info to</param>
        private static void ParseSite(ref ModInfo modInfo)
        {
            var htmlDoc = new HtmlWeb().Load(GetPathToDownloads(modInfo.ModURL));
            htmlDoc.OptionFixNestedTags = true;

            // To scrape the fields, now using HtmlAgilityPack and XPATH search strings.
            // Easy way to get XPATH search: use chrome, inspect element, highlight the needed data and right-click and copy XPATH
            HtmlNode authorNode = htmlDoc.DocumentNode.SelectSingleNode("//*[@id='repo-owner-link']");
            HtmlNode nameNode = htmlDoc.DocumentNode.SelectSingleNode("//*[@id='content']/div[1]/div[1]/div[1]/header/div/div[2]/h1/a");
            HtmlNode updateNode = htmlDoc.DocumentNode.SelectSingleNode("//*[@id='uploaded-files']/tbody/tr[2]/td[5]/div/time");
            HtmlNode downloadNode = htmlDoc.DocumentNode.SelectSingleNode("//*[@id='uploaded-files']/tbody/tr[2]/td[4]");

            modInfo.Author = authorNode.InnerHtml;
            modInfo.Name = nameNode.InnerHtml;
            modInfo.ChangeDateAsDateTime = DateTime.Parse(updateNode.Attributes["datetime"].Value);
            modInfo.Downloads = downloadNode.InnerHtml;
        }
        /// <summary>
        /// Downloads the mod.
        /// </summary>
        /// <param name="modInfo">The infos of the mod. Must have at least ModURL and LocalPath</param>
        /// <param name="downloadProgressCallback">Callback function for download progress.</param>
        /// <returns>True if the mod was downloaded.</returns>
        public bool DownloadMod(ref ModInfo modInfo, DownloadProgressCallback downloadProgressCallback = null)
        {
            if (modInfo == null)
                return false;

            string downloadUrl = GetDownloadPath(GetPathToDownloads(modInfo.ModURL));
            modInfo.LocalPath = Path.Combine(OptionsController.DownloadPath, downloadUrl.Split("/").Last());
            Www.DownloadFile(downloadUrl, modInfo.LocalPath, downloadProgressCallback);

            return File.Exists(modInfo.LocalPath);
        }
 /// <summary>
 /// Downloads the mod.
 /// </summary>
 /// <param name="modInfo">The infos of the mod. Must have at least ModURL and LocalPath</param>
 /// <param name="downloadProgressCallback">Callback function for download progress.</param>
 /// <returns>True if the mod was downloaded.</returns>
 public bool DownloadMod(ref ModInfo modInfo, DownloadProgressCallback downloadProgressCallback = null)
 {
     ISiteHandler curseForge = SiteHandlerManager.GetSiteHandlerByName("CurseForge");
     return curseForge.DownloadMod(ref modInfo, downloadProgressCallback);
 }
        /// <summary>
        /// Creates nodes from the ModInfos and adds the nodes to the ModSelection.
        /// </summary>
        /// <param name="modInfos">The nodes to add.</param>
        /// <returns>List of added mods.</returns>
        internal static List<ModNode> AddMods(ModInfo[] modInfos, bool showCollisionDialog, AsyncTask<List<ModNode>> asyncJob = null)
        {
            int doneCount = 0;
            List<ModNode> addedMods = new List<ModNode>();

            foreach (ModInfo modInfo in modInfos)
            {
                Messenger.AddInfo(Constants.SEPARATOR);
                Messenger.AddInfo(string.Format(Messages.MSG_START_ADDING_0, modInfo.Name));
                Messenger.AddInfo(Constants.SEPARATOR);

                try
                {
                    // already added?
                    ModNode newNode = null;
                    ModNode mod = (string.IsNullOrEmpty(modInfo.ProductID)) ? null : Model[modInfo.ProductID, modInfo.SiteHandlerName];
                    if (mod == null && !Model.ContainsLocalPath(modInfo.LocalPath))
                    {
                        try
                        {
                            if (modInfo.LocalPath.EndsWith(Constants.EXT_CRAFT, StringComparison.CurrentCultureIgnoreCase) && File.Exists(modInfo.LocalPath))
                                modInfo.LocalPath = ModZipCreator.CreateZipOfCraftFile(modInfo.LocalPath);

                            newNode = ModNodeHandler.CreateModNode(modInfo);
                            if (newNode != null)
                            {
                                Model.AddMod(newNode);
                                Messenger.AddInfo(string.Format(Messages.MSG_MOD_ADDED_0, newNode.Text));
                            }
                        }
                        catch (Exception ex)
                        {
                            Messenger.AddError(string.Format(Messages.MSG_MOD_ERROR_WHILE_READ_ZIP_0_ERROR_MSG_1, string.Empty, ex.Message), ex);
                        }
                    }
                    else if (mod != null && (mod.IsOutdated || modInfo.CreationDateAsDateTime > mod.CreationDateAsDateTime) &&
                             OptionsController.ModUpdateBehavior != ModUpdateBehavior.Manualy)
                    {
                        newNode = UpdateMod(modInfo, mod);
                    }
                    else
                    {
                        View.InvokeIfRequired(() =>
                        {
                            StringBuilder sb = new StringBuilder();
                            sb.AppendLine(string.Format(Messages.MSG_MOD_ALREADY_ADDED, modInfo.Name));
                            sb.AppendLine();
                            sb.AppendLine(Messages.MSG_SHOULD_MOD_REPLACED);
                            if (MessageBox.Show(View, sb.ToString(), Messages.MSG_TITLE_ATTENTION, MessageBoxButtons.YesNo) ==
                                DialogResult.Yes)
                            {
                                ModNode outdatedMod = Model[modInfo.LocalPath];
                                Messenger.AddInfo(string.Format(Messages.MSG_REPLACING_MOD_0, outdatedMod.Text));

                                newNode = UpdateMod(modInfo, outdatedMod);
                                Messenger.AddInfo(string.Format(Messages.MSG_MOD_0_REPLACED, newNode.Text));
                            }
                        });
                    }

                    if (newNode != null)
                        addedMods.Add(newNode);

                    newNode = null;

                    if (asyncJob != null)
                        asyncJob.PercentFinished = doneCount += 1;
                }
                catch (Exception ex)
                {
                    MessageBox.Show(View, ex.Message, Messages.MSG_TITLE_ERROR);
                    Messenger.AddError(string.Format(Messages.MSG_ADD_MOD_FAILED_0, modInfo.Name), ex);
                }

                InvalidateView();

                Messenger.AddInfo(Constants.SEPARATOR);
            }

            return addedMods;
        }
        /// <summary>
        /// Creates nodes from the ModInfos and adds the nodes to the ModSelection.
        /// </summary>
        /// <param name="modInfos">The nodes to add.</param>
        internal static void AddModsAsync(ModInfo[] modInfos, bool showCollisionDialog = true)
        {
            if (modInfos.Length <= 0)
            {
                Messenger.AddError(Messages.MSG_ADD_MODS_FAILED_PARAM_EMPTY_MODINFOS);
                return;
            }

            EventDistributor.InvokeAsyncTaskStarted(Instance);
            View.SetEnabledOfAllControls(false);
            View.SetProgressBarStates(true, modInfos.Length, 0);

            AsyncTask<List<ModNode>> asnyJob = new AsyncTask<List<ModNode>>();
            asnyJob.SetCallbackFunctions(() =>
            {
                return AddMods(modInfos, showCollisionDialog, asnyJob);
            },
                (result, ex) =>
                {
                    EventDistributor.InvokeAsyncTaskDone(Instance);
                    View.SetEnabledOfAllControls(true);
                    View.SetProgressBarStates(false);
                },
                (percentage) =>
                {
                    View.SetProgressBarStates(true, modInfos.Length, percentage);
                });
            asnyJob.Run();
        }
        /// <summary>
        /// Gets the ModInfo from a ImportInfo class.
        /// </summary>
        /// <param name="importInfo">The ImportInfo class to get the ModInfos from.</param>
        /// <returns>The ModInfo from a ImportInfo class.</returns>
        private static ModInfo GetModInfo(ImportInfo importInfo)
        {
            ModInfo modInfo = new ModInfo();
            if (importInfo.ModInfo != null)
                modInfo = importInfo.ModInfo;
            else
            {
                modInfo.ModURL = importInfo.ModURL;
                modInfo.AdditionalURL = importInfo.AdditionalURL;
                modInfo.LocalPath = importInfo.LocalPath;
                modInfo.Name = importInfo.Name;
                modInfo.ProductID = importInfo.ProductID;
                modInfo.SiteHandlerName = importInfo.SiteHandlerName;
            }

            return modInfo;
        }
 private string GetDownloadURL(ModInfo modInfo)
 {
     return modInfo.ModURL.EndsWith("/") ? modInfo.ModURL + "download/" + modInfo.Version : modInfo.ModURL + "/download/" + modInfo.Version;
 }
Example #20
0
 /// <summary>
 /// Creates a new instance of the ModNode class.
 /// </summary>
 public ModNode(ModInfo modInfo)
 {
     ModInfo = modInfo;
 }
 private string GetDownloadName(ModInfo modInfo)
 {
     return (modInfo.Name.Replace(' ', '_') + '-' + modInfo.Version + ".zip");
 }
        /// <summary>
        /// Takes a curse project site, fetches the page, and extracts mod info from the page
        /// </summary>
        /// <param name="url">URL to a kerbal.curseforge.com project</param>
        /// <param name="modInfo">Stores the extracted mod data</param>
        /// <returns>Returns true if successfully extracts data</returns>
        private bool ParseSite(string url, ref ModInfo modInfo)
        {
            // changed to use the curse page as it provides the same info but also game version
            // there's no good way to get a mod version from curse. Could use file name? Is using update date (best method?)
            string cursePage = "http://www.curse.com/ksp-mods/kerbal/" + new Uri(url).Segments[2];
            HtmlWeb web = new HtmlWeb();
            HtmlDocument htmlDoc = web.Load(cursePage);
            htmlDoc.OptionFixNestedTags = true;

            // To scrape the fields, now using HtmlAgilityPack and XPATH search strings.
            // Easy way to get XPATH search: use chrome, inspect element, highlight the needed data and right-click and copy XPATH
            HtmlNode nameNode = htmlDoc.DocumentNode.SelectSingleNode("//*[@id='project-overview']/div/div[1]/h2/span/span/span");
            HtmlNode idNode = htmlDoc.DocumentNode.SelectSingleNode("//*[@id='project-overview']/div/div[2]/div/div/div[1]/div[2]/ul[2]/li[8]/a");
            HtmlNode createNode = htmlDoc.DocumentNode.SelectSingleNode("//*[@id='project-overview']/div/div[2]/div/div/div[1]/div[2]/ul[2]/li[6]/abbr");
            HtmlNode updateNode = htmlDoc.DocumentNode.SelectSingleNode("//*[@id='project-overview']/div/div[2]/div/div/div[1]/div[2]/ul[2]/li[5]/abbr");
            HtmlNode downloadNode = htmlDoc.DocumentNode.SelectSingleNode("//*[@id='project-overview']/div/div[2]/div/div/div[1]/div[2]/ul[2]/li[4]");
            HtmlNode authorNode = htmlDoc.DocumentNode.SelectSingleNode("//*[@id='project-overview']/div/div[2]/div/div/div[1]/div[2]/ul[1]/li[1]/a");
            HtmlNode gameVersionNode = htmlDoc.DocumentNode.SelectSingleNode("//*[@id='project-overview']/div/div[2]/div/div/div[1]/div[2]/ul[2]/li[3]");

            var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); // Curse stores the date as both text and as Epoch. Go for the most precise value (Epoch).

            if (nameNode == null)
                return false;
            
            modInfo.Name = nameNode.InnerHtml;
            modInfo.ProductID = idNode.Attributes["href"].Value.Substring(idNode.Attributes["href"].Value.LastIndexOf("/") + 1);
            modInfo.CreationDateAsDateTime = epoch.AddSeconds(Convert.ToDouble(createNode.Attributes["data-epoch"].Value));
            modInfo.ChangeDateAsDateTime = epoch.AddSeconds(Convert.ToDouble(updateNode.Attributes["data-epoch"].Value));
            modInfo.Downloads = downloadNode.InnerHtml.Split(" ")[0];
            modInfo.Author = authorNode.InnerHtml;
            modInfo.KSPVersion = gameVersionNode.InnerHtml.Split(" ")[1];
            return true;
            
            // more infos could be parsed here (like: short description, Tab content (overview, installation, ...), comments, ...)
        }
        /// <summary>
        /// Gets the content of the site of the passed URL and parses it for ModInfos.
        /// </summary>
        /// <param name="url">The URL of the site to parse the ModInfos from.</param>
        /// <returns>The ModInfos parsed from the site of the passed URL.</returns>
        public ModInfo GetModInfo(string url)
        {
            string modInfoUrl = MODINFO_URL + (url.Split(new string[] { "/" }, StringSplitOptions.None).ToList())[4];

            if (string.IsNullOrEmpty(modInfoUrl))
                return null;

            string content = Www.Load(modInfoUrl);
            if (string.IsNullOrEmpty(content))
                return null;

            JObject jObject = JObject.Parse(content);

            var modInfo = new ModInfo
            {
                SiteHandlerName = Name,
                ModURL = url,
                ProductID = GetString(jObject["id"]),
                Name = GetString(jObject["name"]),
                Downloads = GetString(jObject["downloads"]),
                Author = GetString(jObject["author"]),
                Version = GetVersion(jObject["versions"] as JToken),
                KSPVersion = GetKSPVersion(jObject["versions"] as JToken)
            };
            ////modInfo.CreationDate = kerbalMod.Versions.Last().Date; // TODO when KS API supports dates from versions

            return modInfo;
        }
 /// <summary>
 /// Checks if updates are available for the passed mod.
 /// </summary>
 /// <param name="modInfo">The ModInfos of the mod to check for updates.</param>
 /// <param name="newModInfo">A reference to a empty ModInfo to write the updated ModInfos to.</param>
 /// <returns>True if there is an update, otherwise false.</returns>
 public bool CheckForUpdates(ModInfo modInfo, ref ModInfo newModInfo)
 {
     newModInfo = GetModInfo(modInfo.ModURL);
     return modInfo.ChangeDateAsDateTime < newModInfo.ChangeDateAsDateTime;
 }
        /// <summary>
        /// Creates a tree of TreeNodeMod nodes that represent the content of a mod archive.
        /// </summary>
        /// <param name="modInfo">The ModInfo of the mod the create a tree for.</param>
        /// <param name="silent">Determines if info messages should be added.</param>
        /// <returns>A tree of TreeNodeMod nodes that represent the content of a mod archive.</returns>
        public static ModNode CreateModNode(ModInfo modInfo, bool silent = false)
        {
            if (File.Exists(modInfo.LocalPath))
            {
                // Get AVC version file informations.
                if (OptionsController.AVCSupportOnOff)
                {
                    AVCInfo avcInfo = TryReadAVCVersionFile(modInfo.LocalPath);
                    if (avcInfo != null)
                        ImportAvcInfo(avcInfo, ref modInfo);
                }

                // Still no name? Use filename then
                if (string.IsNullOrEmpty(modInfo.Name))
                    modInfo.Name = Path.GetFileNameWithoutExtension(modInfo.LocalPath);

                ModNode node = new ModNode(modInfo);
                using (IArchive archive = ArchiveFactory.Open(modInfo.LocalPath))
                {
                    char seperator = '/';
                    string extension = Path.GetExtension(modInfo.LocalPath);
                    if (extension != null && extension.Equals(Constants.EXT_RAR, StringComparison.CurrentCultureIgnoreCase))
                        seperator = '\\';

                    // create a TreeNode for every archive entry
                    foreach (IArchiveEntry entry in archive.Entries)
                        CreateModNode(entry.FilePath, node, seperator, entry.IsDirectory, silent);
                }

                // Destination detection
                switch (OptionsController.DestinationDetectionType)
                {
                    case DestinationDetectionType.SmartDetection:
                        // Find installation root node (first folder that contains (Parts or Plugins or ...)
                        if (!FindAndSetDestinationPaths(node) && !silent)
                            Messenger.AddInfo(string.Format(Messages.MSG_ROOT_NOT_FOUND_0, node.Text));

                        if (OptionsController.CopyToGameData)
                        {
                            if (!silent)
                                Messenger.AddInfo(string.Format(Messages.MSG_DESTINATION_0_SET_TO_GAMEDATA, node.Text));
                            SetDestinationPaths(node, KSPPathHelper.GetPath(KSPPaths.GameData));
                        }
                        break;
                    case DestinationDetectionType.SimpleDump:
                        if (!silent)
                            Messenger.AddInfo(string.Format(Messages.MSG_DESTINATION_0_SET_TO_GAMEDATA, node.Text));
                        SetDestinationPaths(node, KSPPathHelper.GetPath(KSPPaths.GameData));
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
                
                SetToolTips(node);
                CheckNodesWithDestination(node);

                return node;
            }
            else
            {
                if (!silent)
                    Messenger.AddInfo(string.Format(Messages.MSG_MOD_ZIP_NOT_FOUND_0, modInfo.LocalPath));
            }

            return null;
        }
        /// <summary>
        /// Checks if updates are available for the passed mod.
        /// </summary>
        /// <param name="modInfo">The ModInfos of the mod to check for updates.</param>
        /// <param name="newModInfo">A reference to an empty ModInfo to write the updated ModInfos to.</param>
        /// <returns>True if there is an update, otherwise false.</returns>
        public bool CheckForUpdates(ModInfo modInfo, ref ModInfo newModInfo)
        {
            newModInfo = GetModInfo(modInfo.ModURL);
            if (string.IsNullOrEmpty(modInfo.Version) && !string.IsNullOrEmpty(newModInfo.Version))
                return true;
            else if (!string.IsNullOrEmpty(modInfo.Version) && !string.IsNullOrEmpty(newModInfo.Version))
                return (VersionComparer.CompareVersions(modInfo.Version, newModInfo.Version) == VersionComparer.Result.AisSmallerB);
            else if (string.IsNullOrEmpty(modInfo.CreationDate) && !string.IsNullOrEmpty(newModInfo.CreationDate))
                return true;
            else if (!string.IsNullOrEmpty(modInfo.CreationDate) && !string.IsNullOrEmpty(newModInfo.CreationDate))
                return modInfo.CreationDateAsDateTime < newModInfo.CreationDateAsDateTime;

            return false;
        }
        private void ParseSite(ref ModInfo modInfo)
        {
            var htmlDoc = new HtmlWeb().Load(modInfo.ModURL);
            htmlDoc.OptionFixNestedTags = true;

            // To scrape the fields, now using HtmlAgilityPack and XPATH search strings.
            // Easy way to get XPATH search: use chrome, inspect element, highlight the needed data and right-click and copy XPATH
            modInfo.ProductID = GetProductID(modInfo.ModURL);
            modInfo.CreationDateAsDateTime = GetCreationDate(htmlDoc);
            modInfo.ChangeDateAsDateTime = GetChangeDate(htmlDoc);
            if (modInfo.ChangeDateAsDateTime == DateTime.MinValue)
                modInfo.ChangeDateAsDateTime = modInfo.CreationDateAsDateTime;
            modInfo.Author = GetAuthor(htmlDoc);
            modInfo.Name = GetModName(htmlDoc);
        }
        /// <summary>
        /// Takes a site url and parses the site for mod info
        /// </summary>
        /// <param name="modInfo">The modInfo to add data to</param>
        public void ParseSite(ref ModInfo modInfo)
        {
            var htmlDoc = new HtmlWeb().Load(GetPathToReleases(modInfo.ModURL));
            htmlDoc.OptionFixNestedTags = true;

            // To scrape the fields, now using HtmlAgilityPack and XPATH search strings.
            // Easy way to get XPATH search: use chrome, inspect element, highlight the needed data and right-click and copy XPATH
            HtmlNode latestRelease = htmlDoc.DocumentNode.SelectSingleNode("//*[@class='release label-latest']");
            HtmlNode versionNode = htmlDoc.DocumentNode.SelectSingleNode("//*[@class='release label-latest']/div[1]/ul/li[1]/a/span[2]");
            if (versionNode == null)
                versionNode = htmlDoc.DocumentNode.SelectSingleNode("//*[@id='js-repo-pjax-container']/div[2]/ul/li[1]/div/div/h3/a/span");
            HtmlNode updateNode = htmlDoc.DocumentNode.SelectSingleNode("//*[@class='release label-latest']/div[2]/div/p/time");
            if (updateNode == null)
                updateNode = htmlDoc.DocumentNode.SelectSingleNode("//*[@id='js-repo-pjax-container']/div[2]/ul/li[1]/span/time");

            if (versionNode == null || updateNode == null)
                Messenger.AddError("Error! Can't parse GitHib version or creation date!");

            if (versionNode != null)
                modInfo.Version = Regex.Replace(versionNode.InnerText, @"[A-z]", string.Empty);
            if (updateNode != null)
                modInfo.ChangeDateAsDateTime = DateTime.Parse(updateNode.Attributes["datetime"].Value);
        }
        /// <summary>
        /// Updates the outdated mod.
        /// Tries to copy the checked state and destination of a mod and its parts, then uninstalls the outdated mod and installs the new one.
        /// </summary>
        /// <param name="newModInfo">The ModeInfo of the new mod.</param>
        /// <param name="outdatedMod">The root ModNode of the outdated mod.</param>
        /// <returns>The updated mod.</returns>
        public static ModNode UpdateMod(ModInfo newModInfo, ModNode outdatedMod)
        {
            ModNode newMod = null;
            try
            {
                Messenger.AddInfo(string.Format(Messages.MSG_UPDATING_MOD_0, outdatedMod.Text));
                newMod = ModNodeHandler.CreateModNode(newModInfo);
                newMod.AdditionalURL = outdatedMod.AdditionalURL;
                newMod.AvcURL = outdatedMod.AvcURL;
                newMod.Note = outdatedMod.Note;
                if (OptionsController.ModUpdateBehavior == ModUpdateBehavior.RemoveAndAdd || (!outdatedMod.IsInstalled && !outdatedMod.HasInstalledChilds))
                {
                    RemoveOutdatedAndAddNewMod(outdatedMod, newMod);
                    View.InvokeIfRequired(() => { newMod._Checked = false; });
                }
                else
                {
                    // Find matching file nodes and copy destination from old to new mod.
                    if (ModNodeHandler.TryCopyDestToMatchingNodes(outdatedMod, newMod))
                    {
                        newMod.ModURL = outdatedMod.ModURL;
                        RemoveOutdatedAndAddNewMod(outdatedMod, newMod);
                        ProcessMods(new ModNode[] { newMod }, true);
                    }
                    else
                    {
                        // No match found -> user must handle update.
                        View.InvokeIfRequired(() => MessageBox.Show(View.ParentForm, string.Format(Messages.MSG_ERROR_UPDATING_MOD_0_FAILED, outdatedMod.Text)));
                    }
                }

                Messenger.AddInfo(string.Format(Messages.MSG_MOD_0_UPDATED, newMod.Text));
            }
            catch (Exception ex)
            {
                Messenger.AddError(string.Format(Messages.MSG_ERROR_WHILE_UPDATING_MOD_0_ERROR_1, outdatedMod.Text, ex.Message), ex);
            }

            return newMod;
        }
        /// <summary>
        /// Downloads the mod.
        /// </summary>
        /// <param name="modInfo">The infos of the mod. Must have at least ModURL and LocalPath</param>
        /// <param name="downloadProgressCallback">Callback function for download progress.</param>
        /// <returns>True if the mod was downloaded.</returns>
        public bool DownloadMod(ref ModInfo modInfo, DownloadProgressCallback downloadProgressCallback = null)
        {
            Messenger.AddError("No download support for KSP Forum mods, update check only!");
            MessageBox.Show("No download support for KSP Forum mods, update check only!", Messages.MSG_TITLE_ATTENTION);
            return false;
            ////if (modInfo == null)
            ////    return false;

            ////string downloadUrl = GetDownloadUrl(modInfo);
            ////modInfo.LocalPath = Path.Combine(OptionsController.DownloadPath, GetDownloadName(downloadUrl));
            ////Www.DownloadFile(downloadUrl, modInfo.LocalPath, downloadProgressHandler);

            ////return File.Exists(modInfo.LocalPath);
        }
        /// <summary>
        /// Adds a mod from HD with given ModInfos.
        /// </summary>
        /// <param name="modInfo">The ModInfos of the mod to add.</param>
        /// <param name="installAfterAdd">Flag that determines if the mod should be installed after adding to the ModSelection.</param>
        /// <returns>The new added mod (maybe null).</returns>
        public static ModNode HandleModAddViaModInfo(ModInfo modInfo, bool installAfterAdd)
        {
            ModNode newMod = null;
            List<ModNode> addedMods = AddMods(new ModInfo[] { modInfo }, true, null);
            if (addedMods.Count > 0 && !string.IsNullOrEmpty(modInfo.Name))
                addedMods[0].Text = modInfo.Name;

            if (installAfterAdd)
                ProcessMods(addedMods.ToArray());

            if (addedMods.Count > 0)
                newMod = addedMods[0];

            return newMod;
        }