using System; using System.IO; using System.Net; using System.Threading; using BizHawk.Client.Common; namespace BizHawk.Client.EmuHawk { public static class UpdateChecker { private static readonly string _latestVersionInfoURL = "http://tasvideos.org/SystemBizHawkReleaseManager.html"; private static readonly TimeSpan _minimumCheckDuration = TimeSpan.FromHours(8); private static bool AutoCheckEnabled { get { return Global.Config.Update_AutoCheckEnabled; } set { Global.Config.Update_AutoCheckEnabled = value; } } private static DateTime? LastCheckTimeUTC { get { return Global.Config.Update_LastCheckTimeUTC; } set { Global.Config.Update_LastCheckTimeUTC = value; } } private static string LatestVersion { get { return Global.Config.Update_LatestVersion; } set { Global.Config.Update_LatestVersion = value; } } private static string IgnoreVersion { get { return Global.Config.Update_IgnoreVersion; } set { Global.Config.Update_IgnoreVersion = value; } } public static void BeginCheck(bool skipCheck = false) { if (skipCheck || String.IsNullOrEmpty(_latestVersionInfoURL) || !AutoCheckEnabled || LastCheckTimeUTC > DateTime.UtcNow - _minimumCheckDuration) { OnCheckComplete(); return; } ThreadPool.QueueUserWorkItem((s) => CheckInternal()); } public static bool IsNewVersionAvailable { get { return AutoCheckEnabled && LatestVersion != IgnoreVersion && ParseVersion(VersionInfo.Mainversion) != 0 && // Avoid notifying if current version string is invalid ParseVersion(LatestVersion) > ParseVersion(VersionInfo.Mainversion); } } public static void IgnoreNewVersion() { IgnoreVersion = LatestVersion; } public static void ResetHistory() { LastCheckTimeUTC = null; LatestVersion = ""; IgnoreVersion = ""; } private static void CheckInternal() { try { string latestVersionInfo = WebUtility.HtmlDecode(DownloadURLAsString(_latestVersionInfoURL)); LatestVersion = GetVersionNumberFromVersionInfo(latestVersionInfo); } catch { // Ignore errors, and fall through to set the last check time to avoid requesting too frequently from the web server } LastCheckTimeUTC = DateTime.UtcNow; OnCheckComplete(); } private static string DownloadURLAsString(string url) { HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); request.KeepAlive = false; using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) using (StreamReader responseStream = new StreamReader(response.GetResponseStream())) { return responseStream.ReadToEnd(); } } private static string GetVersionNumberFromVersionInfo(string info) { string versionNumber = GetTextFromTag(info, "VersionNumber"); return (versionNumber != null && ParseVersion(versionNumber) != 0) ? versionNumber : ""; } private static string GetTextFromTag(string info, string tagName) { string openTag = "[" + tagName + "]"; string closeTag = "[/" + tagName + "]"; int start = info.IndexOf(openTag, StringComparison.OrdinalIgnoreCase); if (start == -1) return null; start += openTag.Length; int end = info.IndexOf(closeTag, start, StringComparison.OrdinalIgnoreCase); if (end == -1) return null; return info.Substring(start, end - start).Trim(); } // Major version goes in the first 16 bits, and so on, up to 4 parts private static ulong ParseVersion(string str) { string[] split = str.Split('.'); if (split.Length > 4) return 0; ulong version = 0; for (int i = 0; i < split.Length; i++) { ushort versionPart; if (!UInt16.TryParse(split[i], out versionPart)) return 0; version |= (ulong)versionPart << (48 - (i * 16)); } return version; } private static void OnCheckComplete() { CheckComplete?.Invoke(null, EventArgs.Empty); } public static event EventHandler CheckComplete; } }