From 00ccb52bef8444d7bbe93a404d98be7287bed31b Mon Sep 17 00:00:00 2001 From: Gang Zhuo Date: Mon, 21 Sep 2015 22:42:16 -0400 Subject: [PATCH 1/3] Add an option for checking updates --- .../Controller/Service/UpdateChecker.cs | 19 ++++--- .../Controller/ShadowsocksController.cs | 6 ++ shadowsocks-csharp/Data/cn.txt | 4 ++ shadowsocks-csharp/Model/Configuration.cs | 2 + shadowsocks-csharp/View/MenuViewController.cs | 57 ++++++++++++++++--- 5 files changed, 71 insertions(+), 17 deletions(-) diff --git a/shadowsocks-csharp/Controller/Service/UpdateChecker.cs b/shadowsocks-csharp/Controller/Service/UpdateChecker.cs index d48c0aae..a6709388 100644 --- a/shadowsocks-csharp/Controller/Service/UpdateChecker.cs +++ b/shadowsocks-csharp/Controller/Service/UpdateChecker.cs @@ -14,9 +14,10 @@ namespace Shadowsocks.Controller { private const string UpdateURL = "https://api.github.com/repos/shadowsocks/shadowsocks-windows/releases"; + public bool NewVersionFound; public string LatestVersionNumber; public string LatestVersionURL; - public event EventHandler NewVersionFound; + public event EventHandler CheckUpdateCompleted; public const string Version = "2.5.8"; @@ -114,17 +115,17 @@ namespace Shadowsocks.Controller } } - if (versions.Count == 0) + if (versions.Count != 0) { - return; + // sort versions + SortVersions(versions); + NewVersionFound = true; + LatestVersionURL = versions[versions.Count - 1]; + LatestVersionNumber = ParseVersionFromURL(LatestVersionURL); } - // sort versions - SortVersions(versions); - LatestVersionURL = versions[versions.Count - 1]; - LatestVersionNumber = ParseVersionFromURL(LatestVersionURL); - if (NewVersionFound != null) + if (CheckUpdateCompleted != null) { - NewVersionFound(this, new EventArgs()); + CheckUpdateCompleted(this, new EventArgs()); } } catch (Exception ex) diff --git a/shadowsocks-csharp/Controller/ShadowsocksController.cs b/shadowsocks-csharp/Controller/ShadowsocksController.cs index 2e2f5528..7e86354c 100755 --- a/shadowsocks-csharp/Controller/ShadowsocksController.cs +++ b/shadowsocks-csharp/Controller/ShadowsocksController.cs @@ -280,6 +280,12 @@ namespace Shadowsocks.Controller } } + public void ToggleCheckingUpdate(bool enabled) + { + _config.autoCheckUpdate = enabled; + Configuration.Save(_config); + } + protected void Reload() { // some logic in configuration updated the config when saving, we need to read it again diff --git a/shadowsocks-csharp/Data/cn.txt b/shadowsocks-csharp/Data/cn.txt index 259081da..3a641952 100644 --- a/shadowsocks-csharp/Data/cn.txt +++ b/shadowsocks-csharp/Data/cn.txt @@ -21,6 +21,9 @@ Show QRCode...=显示二维码... Scan QRCode from Screen...=扫描屏幕上的二维码... Availability Statistics=统计可用性 Show Logs...=显示日志... +Updates...=更新... +Check Updates...=检查更新 +Automatically Check Updates=自动检查更新 About...=关于... Quit=退出 Edit Servers=编辑服务器 @@ -78,6 +81,7 @@ Password can not be blank=密码不能为空 Port out of range=端口超出范围 Port can't be 8123=端口不能为 8123 Shadowsocks {0} Update Found=Shadowsocks {0} 更新 +No update is available=没有可用的更新 Click here to download=点击这里下载 Shadowsocks is here=Shadowsocks 在这里 You can turn on/off Shadowsocks in the context menu=可以在右键菜单中开关 Shadowsocks diff --git a/shadowsocks-csharp/Model/Configuration.cs b/shadowsocks-csharp/Model/Configuration.cs index 1ccba56c..35ee5235 100755 --- a/shadowsocks-csharp/Model/Configuration.cs +++ b/shadowsocks-csharp/Model/Configuration.cs @@ -23,6 +23,7 @@ namespace Shadowsocks.Model public string pacUrl; public bool useOnlinePac; public bool availabilityStatistics; + public bool autoCheckUpdate; private static string CONFIG_FILE = "gui-config.json"; @@ -76,6 +77,7 @@ namespace Shadowsocks.Model index = 0, isDefault = true, localPort = 1080, + autoCheckUpdate = true, configs = new List() { GetDefaultServer() diff --git a/shadowsocks-csharp/View/MenuViewController.cs b/shadowsocks-csharp/View/MenuViewController.cs index c8a4c0eb..441a27b2 100755 --- a/shadowsocks-csharp/View/MenuViewController.cs +++ b/shadowsocks-csharp/View/MenuViewController.cs @@ -26,6 +26,7 @@ namespace Shadowsocks.View private ContextMenu contextMenu1; private bool _isFirstRun; + private bool _isStartupChecking; private MenuItem enableItem; private MenuItem modeItem; private MenuItem AutoStartupItem; @@ -42,6 +43,7 @@ namespace Shadowsocks.View private MenuItem updateFromGFWListItem; private MenuItem editGFWUserRuleItem; private MenuItem editOnlinePACItem; + private MenuItem autoCheckUpdatesToggleItem; private ConfigForm configForm; private string _urlToOpen; @@ -68,13 +70,19 @@ namespace Shadowsocks.View _notifyIcon.MouseDoubleClick += notifyIcon1_DoubleClick; this.updateChecker = new UpdateChecker(); - updateChecker.NewVersionFound += updateChecker_NewVersionFound; + updateChecker.CheckUpdateCompleted += updateChecker_CheckUpdateCompleted; LoadCurrentConfiguration(); - updateChecker.CheckUpdate(controller.GetConfigurationCopy()); + Configuration config = controller.GetConfigurationCopy(); - if (controller.GetConfigurationCopy().isDefault) + if (config.autoCheckUpdate) + { + _isStartupChecking = true; + updateChecker.CheckUpdate(config); + } + + if (config.isDefault) { _isFirstRun = true; ShowConfigForm(); @@ -182,6 +190,11 @@ namespace Shadowsocks.View this.ShareOverLANItem = CreateMenuItem("Allow Clients from LAN", new EventHandler(this.ShareOverLANItem_Click)), new MenuItem("-"), CreateMenuItem("Show Logs...", new EventHandler(this.ShowLogItem_Click)), + CreateMenuGroup("Updates...", new MenuItem[] { + CreateMenuItem("Check Updates...", new EventHandler(this.checkUpdatesItem_Click)), + new MenuItem("-"), + this.autoCheckUpdatesToggleItem = CreateMenuItem("Automatically Check Updates", new EventHandler(this.autoCheckUpdatesToggleItem_Click)), + }), CreateMenuItem("About...", new EventHandler(this.AboutItem_Click)), new MenuItem("-"), CreateMenuItem("Quit", new EventHandler(this.Quit_Click)) @@ -238,11 +251,20 @@ namespace Shadowsocks.View ShowBalloonTip(I18N.GetString("Shadowsocks"), result, ToolTipIcon.Info, 1000); } - void updateChecker_NewVersionFound(object sender, EventArgs e) + void updateChecker_CheckUpdateCompleted(object sender, EventArgs e) { - ShowBalloonTip(String.Format(I18N.GetString("Shadowsocks {0} Update Found"), updateChecker.LatestVersionNumber), I18N.GetString("Click here to download"), ToolTipIcon.Info, 5000); - _notifyIcon.BalloonTipClicked += notifyIcon1_BalloonTipClicked; - _isFirstRun = false; + if (updateChecker.NewVersionFound) + { + ShowBalloonTip(String.Format(I18N.GetString("Shadowsocks {0} Update Found"), updateChecker.LatestVersionNumber), I18N.GetString("Click here to download"), ToolTipIcon.Info, 5000); + _notifyIcon.BalloonTipClicked += notifyIcon1_BalloonTipClicked; + _isFirstRun = false; + } + else if (!_isStartupChecking) + { + ShowBalloonTip(I18N.GetString("Shadowsocks"), I18N.GetString("No update is available"), ToolTipIcon.Info, 5000); + _isFirstRun = false; + } + _isStartupChecking = false; } void notifyIcon1_BalloonTipClicked(object sender, EventArgs e) @@ -266,6 +288,7 @@ namespace Shadowsocks.View onlinePACItem.Checked = onlinePACItem.Enabled && config.useOnlinePac; localPACItem.Checked = !onlinePACItem.Checked; UpdatePACItemsEnabledStatus(); + UpdateUpdateMenu(); } private void UpdateServersMenu() @@ -343,7 +366,7 @@ namespace Shadowsocks.View if (_isFirstRun) { _notifyIcon.BalloonTipTitle = I18N.GetString("Shadowsocks is here"); - _notifyIcon.BalloonTipText = I18N.GetString("You can turn on/off Shadowsocks in the context menu"); + _notifyIcon.BalloonTipText = I18N.GetString("You can turn on/off Shadowsocks in the context menu"); _notifyIcon.BalloonTipIcon = ToolTipIcon.Info; _notifyIcon.ShowBalloonTip(0); _isFirstRun = false; @@ -591,5 +614,23 @@ namespace Shadowsocks.View this.editOnlinePACItem.Enabled = true; } } + + private void UpdateUpdateMenu() + { + Configuration configuration = controller.GetConfigurationCopy(); + autoCheckUpdatesToggleItem.Checked = configuration.autoCheckUpdate; + } + + private void autoCheckUpdatesToggleItem_Click(object sender, EventArgs e) + { + Configuration configuration = controller.GetConfigurationCopy(); + controller.ToggleCheckingUpdate(!configuration.autoCheckUpdate); + UpdateUpdateMenu(); + } + + private void checkUpdatesItem_Click(object sender, EventArgs e) + { + updateChecker.CheckUpdate(controller.GetConfigurationCopy()); + } } } From ab79dbce0e9d70e42c46488c08c1f7bf3e0dbe3b Mon Sep 17 00:00:00 2001 From: Gang Zhuo Date: Mon, 21 Sep 2015 23:41:28 -0400 Subject: [PATCH 2/3] Download updates automatically --- .../Controller/Service/UpdateChecker.cs | 216 ++++++++++++------ shadowsocks-csharp/Data/cn.txt | 2 +- shadowsocks-csharp/View/MenuViewController.cs | 5 +- 3 files changed, 149 insertions(+), 74 deletions(-) diff --git a/shadowsocks-csharp/Controller/Service/UpdateChecker.cs b/shadowsocks-csharp/Controller/Service/UpdateChecker.cs index a6709388..0955b255 100644 --- a/shadowsocks-csharp/Controller/Service/UpdateChecker.cs +++ b/shadowsocks-csharp/Controller/Service/UpdateChecker.cs @@ -1,138 +1,212 @@ -using Shadowsocks.Model; -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Net; using System.Reflection; using System.Text; using System.Text.RegularExpressions; +using System.IO; using SimpleJson; +using Shadowsocks.Model; +using Shadowsocks.Util; + namespace Shadowsocks.Controller { public class UpdateChecker { private const string UpdateURL = "https://api.github.com/repos/shadowsocks/shadowsocks-windows/releases"; + private const string UserAgent = "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.3319.102 Safari/537.36"; + private Configuration config; public bool NewVersionFound; public string LatestVersionNumber; + public string LatestVersionName; public string LatestVersionURL; + public string LatestVersionLocalName; public event EventHandler CheckUpdateCompleted; public const string Version = "2.5.8"; public void CheckUpdate(Configuration config) { - // TODO test failures - WebClient http = new WebClient(); - http.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.3319.102 Safari/537.36"); - http.Proxy = new WebProxy(IPAddress.Loopback.ToString(), config.localPort); - http.DownloadStringCompleted += http_DownloadStringCompleted; - http.DownloadStringAsync(new Uri(UpdateURL)); + this.config = config; + + try + { + WebClient http = CreateWebClient(); + http.DownloadStringCompleted += http_DownloadStringCompleted; + http.DownloadStringAsync(new Uri(UpdateURL)); + } + catch (Exception ex) + { + Logging.LogUsefulException(ex); + } } - public static int CompareVersion(string l, string r) + private void http_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e) { - var ls = l.Split('.'); - var rs = r.Split('.'); - for (int i = 0; i < Math.Max(ls.Length, rs.Length); i++) + try { - int lp = (i < ls.Length) ? int.Parse(ls[i]) : 0; - int rp = (i < rs.Length) ? int.Parse(rs[i]) : 0; - if (lp != rp) + string response = e.Result; + + JsonArray result = (JsonArray)SimpleJson.SimpleJson.DeserializeObject(e.Result); + + List asserts = new List(); + foreach (JsonObject release in result) { - return lp - rp; + if ((bool)release["prerelease"]) + { + continue; + } + foreach (JsonObject asset in (JsonArray)release["assets"]) + { + Asset ass = new Asset(); + ass.Parse(asset); + if (ass.IsNewVersion(Version)) + { + asserts.Add(ass); + } + } + } + + if (asserts.Count != 0) + { + SortByVersions(asserts); + Asset asset = asserts[asserts.Count - 1]; + NewVersionFound = true; + LatestVersionURL = asset.browser_download_url; + LatestVersionNumber = asset.version; + LatestVersionName = asset.name; + + startDownload(); + } + else if (CheckUpdateCompleted != null) + { + CheckUpdateCompleted(this, new EventArgs()); } } - return 0; + catch (Exception ex) + { + Logging.LogUsefulException(ex); + } } - public class VersionComparer : IComparer + private void startDownload() { - // Calls CaseInsensitiveComparer.Compare with the parameters reversed. - public int Compare(string x, string y) + try + { + string temppath = Utils.GetTempPath(); + LatestVersionLocalName = Path.Combine(temppath, LatestVersionName); + WebClient http = CreateWebClient(); + http.DownloadFileCompleted += Http_DownloadFileCompleted; + http.DownloadFileAsync(new Uri(LatestVersionURL), LatestVersionLocalName); + } + catch (Exception ex) { - return CompareVersion(ParseVersionFromURL(x), ParseVersionFromURL(y)); + Logging.LogUsefulException(ex); } } - private static string ParseVersionFromURL(string url) + private void Http_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e) { - Match match = Regex.Match(url, @".*Shadowsocks-win.*?-([\d\.]+)\.\w+", RegexOptions.IgnoreCase); - if (match.Success) + try { - if (match.Groups.Count == 2) + if(e.Error != null) { - return match.Groups[1].Value; + Logging.LogUsefulException(e.Error); + return; + } + if (CheckUpdateCompleted != null) + { + CheckUpdateCompleted(this, new EventArgs()); } } - return null; + catch (Exception ex) + { + Logging.LogUsefulException(ex); + } } - private void SortVersions(List versions) + private WebClient CreateWebClient() { - versions.Sort(new VersionComparer()); + WebClient http = new WebClient(); + http.Headers.Add("User-Agent", UserAgent); + http.Proxy = new WebProxy(IPAddress.Loopback.ToString(), config.localPort); + return http; } - private bool IsNewVersion(string url) + private void SortByVersions(List asserts) { - if (url.IndexOf("prerelease") >= 0) - { - return false; - } - string version = ParseVersionFromURL(url); - if (version == null) - { - return false; - } - string currentVersion = Version; - - return CompareVersion(version, currentVersion) > 0; + asserts.Sort(new VersionComparer()); } - private void http_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e) + class Asset { - try + public bool prerelease; + public string name; + public string version; + public string browser_download_url; + + public bool IsNewVersion(string currentVersion) { - string response = e.Result; + if (prerelease) + { + return false; + } + if (version == null) + { + return false; + } + return CompareVersion(version, currentVersion) > 0; + } - JsonArray result = (JsonArray)SimpleJson.SimpleJson.DeserializeObject(e.Result); + public void Parse(JsonObject asset) + { + name = (string)asset["name"]; + browser_download_url = (string)asset["browser_download_url"]; + version = ParseVersionFromURL(browser_download_url); + prerelease = browser_download_url.IndexOf("prerelease") >= 0; + } - List versions = new List(); - foreach (JsonObject release in result) + private static string ParseVersionFromURL(string url) + { + Match match = Regex.Match(url, @".*Shadowsocks-win.*?-([\d\.]+)\.\w+", RegexOptions.IgnoreCase); + if (match.Success) { - if ((bool)release["prerelease"]) - { - continue; - } - foreach (JsonObject asset in (JsonArray)release["assets"]) + if (match.Groups.Count == 2) { - string url = (string)asset["browser_download_url"]; - if (IsNewVersion(url)) - { - versions.Add(url); - } + return match.Groups[1].Value; } } + return null; + } - if (versions.Count != 0) - { - // sort versions - SortVersions(versions); - NewVersionFound = true; - LatestVersionURL = versions[versions.Count - 1]; - LatestVersionNumber = ParseVersionFromURL(LatestVersionURL); - } - if (CheckUpdateCompleted != null) + public static int CompareVersion(string l, string r) + { + var ls = l.Split('.'); + var rs = r.Split('.'); + for (int i = 0; i < Math.Max(ls.Length, rs.Length); i++) { - CheckUpdateCompleted(this, new EventArgs()); + int lp = (i < ls.Length) ? int.Parse(ls[i]) : 0; + int rp = (i < rs.Length) ? int.Parse(rs[i]) : 0; + if (lp != rp) + { + return lp - rp; + } } + return 0; } - catch (Exception ex) + } + + class VersionComparer : IComparer + { + // Calls CaseInsensitiveComparer.Compare with the parameters reversed. + public int Compare(Asset x, Asset y) { - Logging.Debug(ex.ToString()); - return; + return Asset.CompareVersion(x.version, y.version); } } + } } diff --git a/shadowsocks-csharp/Data/cn.txt b/shadowsocks-csharp/Data/cn.txt index 3a641952..f2accf58 100644 --- a/shadowsocks-csharp/Data/cn.txt +++ b/shadowsocks-csharp/Data/cn.txt @@ -82,7 +82,7 @@ Port out of range=端口超出范围 Port can't be 8123=端口不能为 8123 Shadowsocks {0} Update Found=Shadowsocks {0} 更新 No update is available=没有可用的更新 -Click here to download=点击这里下载 +Click here to update=点击这里升级 Shadowsocks is here=Shadowsocks 在这里 You can turn on/off Shadowsocks in the context menu=可以在右键菜单中开关 Shadowsocks System Proxy Enabled=系统代理已启用 diff --git a/shadowsocks-csharp/View/MenuViewController.cs b/shadowsocks-csharp/View/MenuViewController.cs index 441a27b2..e729d943 100755 --- a/shadowsocks-csharp/View/MenuViewController.cs +++ b/shadowsocks-csharp/View/MenuViewController.cs @@ -255,7 +255,7 @@ namespace Shadowsocks.View { if (updateChecker.NewVersionFound) { - ShowBalloonTip(String.Format(I18N.GetString("Shadowsocks {0} Update Found"), updateChecker.LatestVersionNumber), I18N.GetString("Click here to download"), ToolTipIcon.Info, 5000); + ShowBalloonTip(String.Format(I18N.GetString("Shadowsocks {0} Update Found"), updateChecker.LatestVersionNumber), I18N.GetString("Click here to update"), ToolTipIcon.Info, 5000); _notifyIcon.BalloonTipClicked += notifyIcon1_BalloonTipClicked; _isFirstRun = false; } @@ -269,8 +269,9 @@ namespace Shadowsocks.View void notifyIcon1_BalloonTipClicked(object sender, EventArgs e) { - System.Diagnostics.Process.Start(updateChecker.LatestVersionURL); _notifyIcon.BalloonTipClicked -= notifyIcon1_BalloonTipClicked; + string argument = "/select, \"" + updateChecker.LatestVersionLocalName + "\""; + System.Diagnostics.Process.Start("explorer.exe", argument); } From 1d1c325ce103b19bf5262f74bc35119aa28d5e96 Mon Sep 17 00:00:00 2001 From: Gang Zhuo Date: Tue, 22 Sep 2015 00:11:12 -0400 Subject: [PATCH 3/3] fix UnitTest --- .../Controller/Service/UpdateChecker.cs | 2 +- test/UnitTest.cs | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/shadowsocks-csharp/Controller/Service/UpdateChecker.cs b/shadowsocks-csharp/Controller/Service/UpdateChecker.cs index 0955b255..643c7014 100644 --- a/shadowsocks-csharp/Controller/Service/UpdateChecker.cs +++ b/shadowsocks-csharp/Controller/Service/UpdateChecker.cs @@ -141,7 +141,7 @@ namespace Shadowsocks.Controller asserts.Sort(new VersionComparer()); } - class Asset + public class Asset { public bool prerelease; public string name; diff --git a/test/UnitTest.cs b/test/UnitTest.cs index bb95fa50..6003770e 100755 --- a/test/UnitTest.cs +++ b/test/UnitTest.cs @@ -13,13 +13,13 @@ namespace test [TestMethod] public void TestCompareVersion() { - Assert.IsTrue(UpdateChecker.CompareVersion("2.3.1.0", "2.3.1") == 0); - Assert.IsTrue(UpdateChecker.CompareVersion("1.2", "1.3") < 0); - Assert.IsTrue(UpdateChecker.CompareVersion("1.3", "1.2") > 0); - Assert.IsTrue(UpdateChecker.CompareVersion("1.3", "1.3") == 0); - Assert.IsTrue(UpdateChecker.CompareVersion("1.2.1", "1.2") > 0); - Assert.IsTrue(UpdateChecker.CompareVersion("2.3.1", "2.4") < 0); - Assert.IsTrue(UpdateChecker.CompareVersion("1.3.2", "1.3.1") > 0); + Assert.IsTrue(UpdateChecker.Asset.CompareVersion("2.3.1.0", "2.3.1") == 0); + Assert.IsTrue(UpdateChecker.Asset.CompareVersion("1.2", "1.3") < 0); + Assert.IsTrue(UpdateChecker.Asset.CompareVersion("1.3", "1.2") > 0); + Assert.IsTrue(UpdateChecker.Asset.CompareVersion("1.3", "1.3") == 0); + Assert.IsTrue(UpdateChecker.Asset.CompareVersion("1.2.1", "1.2") > 0); + Assert.IsTrue(UpdateChecker.Asset.CompareVersion("2.3.1", "2.4") < 0); + Assert.IsTrue(UpdateChecker.Asset.CompareVersion("1.3.2", "1.3.1") > 0); } private void RunEncryptionRound(IEncryptor encryptor, IEncryptor decryptor)