- Add an option for checking updates - download updates automaticallytags/3.0
@@ -1,137 +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 event EventHandler NewVersionFound; | |||
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<Asset> asserts = new List<Asset>(); | |||
foreach (JsonObject release in result) | |||
{ | |||
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) | |||
{ | |||
return lp - rp; | |||
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()); | |||
} | |||
} | |||
catch (Exception ex) | |||
{ | |||
Logging.LogUsefulException(ex); | |||
} | |||
return 0; | |||
} | |||
public class VersionComparer : IComparer<string> | |||
private void startDownload() | |||
{ | |||
// Calls CaseInsensitiveComparer.Compare with the parameters reversed. | |||
public int Compare(string x, string y) | |||
try | |||
{ | |||
return CompareVersion(ParseVersionFromURL(x), ParseVersionFromURL(y)); | |||
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) | |||
{ | |||
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) | |||
{ | |||
Logging.LogUsefulException(e.Error); | |||
return; | |||
} | |||
if (CheckUpdateCompleted != null) | |||
{ | |||
return match.Groups[1].Value; | |||
CheckUpdateCompleted(this, new EventArgs()); | |||
} | |||
} | |||
return null; | |||
catch (Exception ex) | |||
{ | |||
Logging.LogUsefulException(ex); | |||
} | |||
} | |||
private void SortVersions(List<string> 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<Asset> 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) | |||
public 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<string> versions = new List<string>(); | |||
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"]) | |||
if (match.Groups.Count == 2) | |||
{ | |||
continue; | |||
} | |||
foreach (JsonObject asset in (JsonArray)release["assets"]) | |||
{ | |||
string url = (string)asset["browser_download_url"]; | |||
if (IsNewVersion(url)) | |||
{ | |||
versions.Add(url); | |||
} | |||
return match.Groups[1].Value; | |||
} | |||
} | |||
return null; | |||
} | |||
if (versions.Count == 0) | |||
{ | |||
return; | |||
} | |||
// sort versions | |||
SortVersions(versions); | |||
LatestVersionURL = versions[versions.Count - 1]; | |||
LatestVersionNumber = ParseVersionFromURL(LatestVersionURL); | |||
if (NewVersionFound != 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++) | |||
{ | |||
NewVersionFound(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<Asset> | |||
{ | |||
// 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); | |||
} | |||
} | |||
} | |||
} |
@@ -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 | |||
@@ -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,7 +81,8 @@ Password can not be blank=密码不能为空 | |||
Port out of range=端口超出范围 | |||
Port can't be 8123=端口不能为 8123 | |||
Shadowsocks {0} Update Found=Shadowsocks {0} 更新 | |||
Click here to download=点击这里下载 | |||
No update is available=没有可用的更新 | |||
Click here to update=点击这里升级 | |||
Shadowsocks is here=Shadowsocks 在这里 | |||
You can turn on/off Shadowsocks in the context menu=可以在右键菜单中开关 Shadowsocks | |||
System Proxy Enabled=系统代理已启用 | |||
@@ -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<Server>() | |||
{ | |||
GetDefaultServer() | |||
@@ -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,17 +251,27 @@ 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 update"), 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) | |||
{ | |||
System.Diagnostics.Process.Start(updateChecker.LatestVersionURL); | |||
_notifyIcon.BalloonTipClicked -= notifyIcon1_BalloonTipClicked; | |||
string argument = "/select, \"" + updateChecker.LatestVersionLocalName + "\""; | |||
System.Diagnostics.Process.Start("explorer.exe", argument); | |||
} | |||
@@ -266,6 +289,7 @@ namespace Shadowsocks.View | |||
onlinePACItem.Checked = onlinePACItem.Enabled && config.useOnlinePac; | |||
localPACItem.Checked = !onlinePACItem.Checked; | |||
UpdatePACItemsEnabledStatus(); | |||
UpdateUpdateMenu(); | |||
} | |||
private void UpdateServersMenu() | |||
@@ -343,7 +367,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 +615,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()); | |||
} | |||
} | |||
} |
@@ -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) | |||