@@ -16,186 +16,84 @@ namespace Shadowsocks.Controller | |||
{ | |||
private const string GFWLIST_URL = "https://autoproxy-gfwlist.googlecode.com/svn/trunk/gfwlist.txt"; | |||
private const int EXPIRE_HOURS = 6; | |||
public IWebProxy proxy = null; | |||
public bool useSystemProxy = true; | |||
public class GfwListChangedArgs : EventArgs | |||
public class GfwListDownloadCompletedArgs : EventArgs | |||
{ | |||
public string[] GfwList { get; set; } | |||
public string Content; | |||
} | |||
public event EventHandler<GfwListChangedArgs> GfwListChanged; | |||
private bool running = false; | |||
private bool closed = false; | |||
private int jobId = 0; | |||
DateTime lastUpdateTimeUtc; | |||
string lastUpdateMd5; | |||
private object locker = new object(); | |||
public event EventHandler<GfwListDownloadCompletedArgs> DownloadCompleted; | |||
public GfwListUpdater() | |||
{ | |||
} | |||
public event ErrorEventHandler Error; | |||
~GfwListUpdater() | |||
public void Download() | |||
{ | |||
Stop(); | |||
WebClient http = new WebClient(); | |||
http.Proxy = proxy; | |||
http.DownloadStringCompleted += http_DownloadStringCompleted; | |||
http.DownloadStringAsync(new Uri(GFWLIST_URL)); | |||
} | |||
public void Start() | |||
protected void ReportError(Exception e) | |||
{ | |||
lock (locker) | |||
if (Error != null) | |||
{ | |||
if (running) | |||
return; | |||
running = true; | |||
closed = false; | |||
jobId++; | |||
new Thread(new ParameterizedThreadStart(UpdateJob)).Start(jobId); | |||
Error(this, new ErrorEventArgs(e)); | |||
} | |||
} | |||
public void Stop() | |||
{ | |||
lock(locker) | |||
{ | |||
closed = true; | |||
running = false; | |||
jobId++; | |||
} | |||
} | |||
public void ScheduleUpdateTime(int delaySeconds) | |||
{ | |||
lock(locker) | |||
{ | |||
lastUpdateTimeUtc = DateTime.UtcNow.AddHours(-1 * EXPIRE_HOURS).AddSeconds(delaySeconds); | |||
} | |||
} | |||
private string DownloadGfwListFile() | |||
private void http_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e) | |||
{ | |||
try | |||
{ | |||
WebClient http = new WebClient(); | |||
http.Proxy = useSystemProxy ? WebRequest.GetSystemWebProxy() : proxy; | |||
return http.DownloadString(new Uri(GFWLIST_URL)); | |||
string response = e.Result; | |||
if (DownloadCompleted != null) | |||
{ | |||
DownloadCompleted(this, new GfwListDownloadCompletedArgs | |||
{ | |||
Content = response | |||
}); | |||
} | |||
} | |||
catch (Exception ex) | |||
{ | |||
Console.WriteLine(ex.ToString()); | |||
ReportError(ex); | |||
} | |||
return null; | |||
} | |||
private bool IsExpire() | |||
public class Parser | |||
{ | |||
lock (locker) | |||
{ | |||
TimeSpan ts = DateTime.UtcNow - lastUpdateTimeUtc; | |||
bool expire = ((int)ts.TotalHours) >= EXPIRE_HOURS; | |||
if (expire) | |||
lastUpdateTimeUtc = DateTime.UtcNow; | |||
return expire; | |||
} | |||
} | |||
private string _Content; | |||
private bool IsJobStop(int currentJobId) | |||
{ | |||
lock (locker) | |||
public string Content | |||
{ | |||
if (!running || closed || currentJobId != this.jobId) | |||
return true; | |||
get { return _Content; } | |||
} | |||
return false; | |||
} | |||
private bool IsGfwListChanged(string content) | |||
{ | |||
byte[] inputBytes = Encoding.UTF8.GetBytes(content); | |||
byte[] md5Bytes = MD5.Create().ComputeHash(inputBytes); | |||
string md5 = ""; | |||
for (int i = 0; i < md5Bytes.Length; i++) | |||
md5 += md5Bytes[i].ToString("x").PadLeft(2, '0'); | |||
if (md5 == lastUpdateMd5) | |||
return false; | |||
lastUpdateMd5 = md5; | |||
return true; | |||
} | |||
private void ParseGfwList(string response) | |||
{ | |||
if (!IsGfwListChanged(response)) | |||
return; | |||
if (GfwListChanged != null) | |||
public Parser(string response) | |||
{ | |||
try | |||
{ | |||
Parser parser = new Parser(response); | |||
GfwListChangedArgs args = new GfwListChangedArgs | |||
{ | |||
GfwList = parser.GetReducedDomains() | |||
}; | |||
GfwListChanged(this, args); | |||
} | |||
catch(Exception ex) | |||
{ | |||
Console.WriteLine(ex.ToString()); | |||
} | |||
byte[] bytes = Convert.FromBase64String(response); | |||
this._Content = Encoding.ASCII.GetString(bytes); | |||
} | |||
} | |||
private void UpdateJob(object state) | |||
{ | |||
int currentJobId = (int)state; | |||
int retryTimes = 3; | |||
while (!IsJobStop(currentJobId)) | |||
public string[] GetValidLines() | |||
{ | |||
if (IsExpire()) | |||
string[] lines = Content.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); | |||
List<string> valid_lines = new List<string>(lines.Length); | |||
foreach (string line in lines) | |||
{ | |||
string response = DownloadGfwListFile(); | |||
if (response != null) | |||
{ | |||
ParseGfwList(response); | |||
} | |||
else if (retryTimes > 0) | |||
{ | |||
ScheduleUpdateTime(30); /*Delay 30 seconds to retry*/ | |||
retryTimes--; | |||
} | |||
else | |||
{ | |||
retryTimes = 3; /* reset retry times, and wait next update time. */ | |||
} | |||
if (line.StartsWith("!") || line.StartsWith("[")) | |||
continue; | |||
valid_lines.Add(line); | |||
} | |||
Thread.Sleep(1000); | |||
} | |||
} | |||
class Parser | |||
{ | |||
public string Content { get; private set; } | |||
public Parser(string response) | |||
{ | |||
byte[] bytes = Convert.FromBase64String(response); | |||
this.Content = Encoding.ASCII.GetString(bytes); | |||
return valid_lines.ToArray(); | |||
} | |||
public string[] GetLines() | |||
{ | |||
return Content.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); | |||
} | |||
/* refer https://github.com/clowwindy/gfwlist2pac/blob/master/gfwlist2pac/main.py */ | |||
public string[] GetDomains() | |||
{ | |||
List<string> lines = new List<string>(GetLines()); | |||
List<string> lines = new List<string>(GetValidLines()); | |||
lines.AddRange(GetBuildIn()); | |||
List<string> domains = new List<string>(lines.Count); | |||
for (int i = 0; i < lines.Count; i++) | |||
@@ -222,7 +120,7 @@ namespace Shadowsocks.Controller | |||
continue; | |||
else if (line.StartsWith("@")) | |||
continue; /*ignore white list*/ | |||
int pos = line.IndexOfAny(new char[] { '/'}); | |||
int pos = line.IndexOfAny(new char[] { '/' }); | |||
if (pos >= 0) | |||
line = line.Substring(0, pos); | |||
if (line.Length > 0) | |||
@@ -238,7 +136,7 @@ namespace Shadowsocks.Controller | |||
List<string> new_domains = new List<string>(domains.Length); | |||
TldIndex tldIndex = GetTldIndex(); | |||
foreach(string domain in domains) | |||
foreach (string domain in domains) | |||
{ | |||
string last_root_domain = null; | |||
int pos; | |||
@@ -246,7 +144,7 @@ namespace Shadowsocks.Controller | |||
last_root_domain = domain.Substring(pos + 1); | |||
if (!tldIndex.Contains(last_root_domain)) | |||
continue; | |||
while(pos > 0) | |||
while (pos > 0) | |||
{ | |||
pos = domain.LastIndexOf('.', pos - 1); | |||
last_root_domain = domain.Substring(pos + 1); | |||
@@ -266,7 +164,7 @@ namespace Shadowsocks.Controller | |||
{ | |||
List<string> list = new List<string>(src.Length); | |||
Dictionary<string, string> dic = new Dictionary<string, string>(src.Length); | |||
foreach(string s in src) | |||
foreach (string s in src) | |||
{ | |||
if (!dic.ContainsKey(s)) | |||
{ | |||
@@ -281,9 +179,9 @@ namespace Shadowsocks.Controller | |||
{ | |||
string[] tlds = null; | |||
byte[] pacGZ = Resources.tld_txt; | |||
byte[] buffer = new byte[1024]; | |||
byte[] buffer = new byte[1024]; | |||
int n; | |||
using(MemoryStream sb = new MemoryStream()) | |||
using (MemoryStream sb = new MemoryStream()) | |||
{ | |||
using (GZipStream input = new GZipStream(new MemoryStream(pacGZ), | |||
CompressionMode.Decompress, false)) | |||
@@ -332,7 +230,7 @@ namespace Shadowsocks.Controller | |||
return buildin; | |||
} | |||
class TldIndex | |||
public class TldIndex | |||
{ | |||
List<string> patterns = new List<string>(); | |||
IDictionary<string, string> dic = new Dictionary<string, string>(); | |||
@@ -355,7 +253,7 @@ namespace Shadowsocks.Controller | |||
{ | |||
if (dic.ContainsKey(tld)) | |||
return true; | |||
foreach(string pattern in patterns) | |||
foreach (string pattern in patterns) | |||
{ | |||
if (Regex.IsMatch(tld, pattern)) | |||
return true; | |||
@@ -365,7 +263,7 @@ namespace Shadowsocks.Controller | |||
} | |||
} | |||
} | |||
@@ -1,6 +1,7 @@ | |||
using Shadowsocks.Model; | |||
using Shadowsocks.Properties; | |||
using System; | |||
using System.Collections; | |||
using System.Collections.Generic; | |||
using System.Diagnostics; | |||
using System.IO; | |||
@@ -21,10 +22,12 @@ namespace Shadowsocks.Controller | |||
Socket _listener; | |||
FileSystemWatcher watcher; | |||
GfwListUpdater gfwlistUpdater; | |||
public event EventHandler PACFileChanged; | |||
public event EventHandler UpdatePACFromGFWListCompleted; | |||
public event ErrorEventHandler UpdatePACFromGFWListError; | |||
public void Start(Configuration configuration) | |||
{ | |||
try | |||
@@ -51,7 +54,6 @@ namespace Shadowsocks.Controller | |||
_listener); | |||
WatchPacFile(); | |||
StartGfwListUpdater(); | |||
} | |||
catch (SocketException) | |||
{ | |||
@@ -62,11 +64,6 @@ namespace Shadowsocks.Controller | |||
public void Stop() | |||
{ | |||
if (gfwlistUpdater != null) | |||
{ | |||
gfwlistUpdater.Stop(); | |||
gfwlistUpdater = null; | |||
} | |||
if (_listener != null) | |||
{ | |||
_listener.Close(); | |||
@@ -146,7 +143,7 @@ namespace Shadowsocks.Controller | |||
using (GZipStream input = new GZipStream(new MemoryStream(pacGZ), | |||
CompressionMode.Decompress, false)) | |||
{ | |||
while((n = input.Read(buffer, 0, buffer.Length)) > 0) | |||
while ((n = input.Read(buffer, 0, buffer.Length)) > 0) | |||
{ | |||
sb.Write(buffer, 0, n); | |||
} | |||
@@ -252,72 +249,103 @@ Connection: Close | |||
return proxy; | |||
} | |||
private void StartGfwListUpdater() | |||
public void UpdatePACFromGFWList() | |||
{ | |||
GfwListUpdater gfwlist = new GfwListUpdater(); | |||
gfwlist.DownloadCompleted += gfwlist_DownloadCompleted; | |||
gfwlist.Error += gfwlist_Error; | |||
gfwlist.proxy = new WebProxy(IPAddress.Loopback.ToString(), 8123); /* use polipo proxy*/ | |||
gfwlist.Download(); | |||
} | |||
private void gfwlist_DownloadCompleted(object sender, GfwListUpdater.GfwListDownloadCompletedArgs e) | |||
{ | |||
if (gfwlistUpdater != null) | |||
GfwListUpdater.Parser parser = new GfwListUpdater.Parser(e.Content); | |||
string[] lines = parser.GetValidLines(); | |||
StringBuilder rules = new StringBuilder(lines.Length * 16); | |||
SerializeRules(lines, rules); | |||
string abpContent = GetAbpContent(); | |||
abpContent = abpContent.Replace("__RULES__", rules.ToString()); | |||
File.WriteAllText(PAC_FILE, abpContent); | |||
if (UpdatePACFromGFWListCompleted != null) | |||
{ | |||
gfwlistUpdater.Stop(); | |||
gfwlistUpdater = null; | |||
UpdatePACFromGFWListCompleted(this, new EventArgs()); | |||
} | |||
} | |||
gfwlistUpdater = new GfwListUpdater(); | |||
gfwlistUpdater.GfwListChanged += gfwlistUpdater_GfwListChanged; | |||
IPEndPoint localEndPoint = (IPEndPoint)_listener.LocalEndPoint; | |||
gfwlistUpdater.proxy = new WebProxy(localEndPoint.Address.ToString(), 8123); | |||
gfwlistUpdater.useSystemProxy = false; | |||
/* Delay 30 seconds, wait proxy start up. */ | |||
gfwlistUpdater.ScheduleUpdateTime(30); | |||
gfwlistUpdater.Start(); | |||
private void gfwlist_Error(object sender, ErrorEventArgs e) | |||
{ | |||
if (UpdatePACFromGFWListError != null) | |||
{ | |||
UpdatePACFromGFWListError(this, e); | |||
} | |||
} | |||
private void gfwlistUpdater_GfwListChanged(object sender, GfwListUpdater.GfwListChangedArgs e) | |||
private string GetAbpContent() | |||
{ | |||
if (e.GfwList == null || e.GfwList.Length == 0) return; | |||
string pacfile = TouchPACFile(); | |||
string pacContent = File.ReadAllText(pacfile); | |||
string oldDomains; | |||
if (ClearPacContent(ref pacContent, out oldDomains)) | |||
byte[] abpGZ = Resources.abp_js; | |||
byte[] buffer = new byte[1024]; // builtin pac gzip size: maximum 100K | |||
int n; | |||
using (MemoryStream sb = new MemoryStream()) | |||
{ | |||
StringBuilder sb = new StringBuilder(); | |||
sb.AppendLine("{"); | |||
for (int i = 0; i < e.GfwList.Length; i++) | |||
{ | |||
if (i == e.GfwList.Length - 1) | |||
sb.AppendFormat("\t\"{0}\": {1}\r\n", e.GfwList[i], 1); | |||
else | |||
sb.AppendFormat("\t\"{0}\": {1},\r\n", e.GfwList[i], 1); | |||
} | |||
sb.Append("}"); | |||
string newDomains = sb.ToString(); | |||
if (!string.Equals(oldDomains, newDomains)) | |||
using (GZipStream input = new GZipStream(new MemoryStream(abpGZ), | |||
CompressionMode.Decompress, false)) | |||
{ | |||
pacContent = pacContent.Replace("__LAST_MODIFIED__", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); | |||
pacContent = pacContent.Replace("__DOMAINS__", newDomains); | |||
File.WriteAllText(pacfile, pacContent); | |||
Console.WriteLine("gfwlist updated - " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); | |||
while ((n = input.Read(buffer, 0, buffer.Length)) > 0) | |||
{ | |||
sb.Write(buffer, 0, n); | |||
} | |||
} | |||
return System.Text.Encoding.UTF8.GetString(sb.ToArray()); | |||
} | |||
else | |||
} | |||
private static void SerializeRules(string[] rules, StringBuilder builder) | |||
{ | |||
builder.Append("[\n"); | |||
bool first = true; | |||
foreach (string rule in rules) | |||
{ | |||
Console.WriteLine("Broken pac file."); | |||
if (!first) | |||
builder.Append(",\n"); | |||
SerializeString(rule, builder); | |||
first = false; | |||
} | |||
builder.Append("\n]"); | |||
} | |||
private bool ClearPacContent(ref string pacContent, out string oldDomains) | |||
private static void SerializeString(string aString, StringBuilder builder) | |||
{ | |||
Regex regex = new Regex("(/\\*.*?\\*/\\s*)?var\\s+domains\\s*=\\s*(\\{(\\s*\"[^\"]*\"\\s*:\\s*\\d+\\s*,)*\\s*(\\s*\"[^\"]*\"\\s*:\\s*\\d+\\s*)\\})", RegexOptions.Singleline); | |||
Match m = regex.Match(pacContent); | |||
if (m.Success) | |||
builder.Append("\t\""); | |||
char[] charArray = aString.ToCharArray(); | |||
for (int i = 0; i < charArray.Length; i++) | |||
{ | |||
oldDomains = m.Result("$2"); | |||
pacContent = regex.Replace(pacContent, "/* Last Modified: __LAST_MODIFIED__ */\r\nvar domains = __DOMAINS__"); | |||
return true; | |||
char c = charArray[i]; | |||
if (c == '"') | |||
builder.Append("\\\""); | |||
else if (c == '\\') | |||
builder.Append("\\\\"); | |||
else if (c == '\b') | |||
builder.Append("\\b"); | |||
else if (c == '\f') | |||
builder.Append("\\f"); | |||
else if (c == '\n') | |||
builder.Append("\\n"); | |||
else if (c == '\r') | |||
builder.Append("\\r"); | |||
else if (c == '\t') | |||
builder.Append("\\t"); | |||
else | |||
builder.Append(c); | |||
} | |||
oldDomains = null; | |||
return false; | |||
} | |||
builder.Append("\""); | |||
} | |||
} | |||
} |
@@ -38,6 +38,10 @@ namespace Shadowsocks.Controller | |||
// when user clicked Edit PAC, and PAC file has already created | |||
public event EventHandler<PathEventArgs> PACFileReadyToOpen; | |||
public event EventHandler UpdatePACFromGFWListCompleted; | |||
public event ErrorEventHandler UpdatePACFromGFWListError; | |||
public event ErrorEventHandler Errored; | |||
public ShadowsocksController() | |||
@@ -156,6 +160,14 @@ namespace Shadowsocks.Controller | |||
return "ss://" + base64; | |||
} | |||
public void UpdatePACFromGFWList() | |||
{ | |||
if (pacServer != null) | |||
{ | |||
pacServer.UpdatePACFromGFWList(); | |||
} | |||
} | |||
protected void Reload() | |||
{ | |||
// some logic in configuration updated the config when saving, we need to read it again | |||
@@ -169,6 +181,8 @@ namespace Shadowsocks.Controller | |||
{ | |||
pacServer = new PACServer(); | |||
pacServer.PACFileChanged += pacServer_PACFileChanged; | |||
pacServer.UpdatePACFromGFWListCompleted += pacServer_UpdatePACFromGFWListCompleted; | |||
pacServer.UpdatePACFromGFWListError += pacServer_UpdatePACFromGFWListError; | |||
} | |||
pacServer.Stop(); | |||
@@ -247,6 +261,18 @@ namespace Shadowsocks.Controller | |||
UpdateSystemProxy(); | |||
} | |||
private void pacServer_UpdatePACFromGFWListCompleted(object sender, EventArgs e) | |||
{ | |||
if (UpdatePACFromGFWListCompleted != null) | |||
UpdatePACFromGFWListCompleted(this, e); | |||
} | |||
private void pacServer_UpdatePACFromGFWListError(object sender, ErrorEventArgs e) | |||
{ | |||
if (UpdatePACFromGFWListError != null) | |||
UpdatePACFromGFWListError(this, e); | |||
} | |||
private void StartReleasingMemory() | |||
{ | |||
_ramThread = new Thread(new ThreadStart(ReleaseMemory)); | |||
@@ -39,3 +39,6 @@ Shadowsocks is here=Shadowsocks 在这里 | |||
You can turn on/off Shadowsocks in the context menu=可以在右键菜单中开关 Shadowsocks | |||
Enabled=已启用代理 | |||
Disabled=已禁用代理 | |||
Update PAC File via gfwlist...=基于 gfwlist 更新 PAC 文件... | |||
Update PAC file failed=更新 PAC 文件失败 | |||
Update PAC file succeed=更新 PAC 文件成功 |
@@ -1,10 +1,10 @@ | |||
//------------------------------------------------------------------------------ | |||
// <auto-generated> | |||
// 此代码由工具生成。 | |||
// 运行时版本:4.0.30319.34014 | |||
// This code was generated by a tool. | |||
// Runtime Version:4.0.30319.18444 | |||
// | |||
// 对此文件的更改可能会导致不正确的行为,并且如果 | |||
// 重新生成代码,这些更改将会丢失。 | |||
// Changes to this file may cause incorrect behavior and will be lost if | |||
// the code is regenerated. | |||
// </auto-generated> | |||
//------------------------------------------------------------------------------ | |||
@@ -13,12 +13,12 @@ namespace Shadowsocks.Properties { | |||
/// <summary> | |||
/// 一个强类型的资源类,用于查找本地化的字符串等。 | |||
/// A strongly-typed resource class, for looking up localized strings, etc. | |||
/// </summary> | |||
// 此类是由 StronglyTypedResourceBuilder | |||
// 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 | |||
// 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen | |||
// (以 /str 作为命令选项),或重新生成 VS 项目。 | |||
// This class was auto-generated by the StronglyTypedResourceBuilder | |||
// class via a tool like ResGen or Visual Studio. | |||
// To add or remove a member, edit your .ResX file then rerun ResGen | |||
// with the /str option, or rebuild your VS project. | |||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] | |||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] | |||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] | |||
@@ -33,7 +33,7 @@ namespace Shadowsocks.Properties { | |||
} | |||
/// <summary> | |||
/// 返回此类使用的缓存的 ResourceManager 实例。 | |||
/// Returns the cached ResourceManager instance used by this class. | |||
/// </summary> | |||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] | |||
internal static global::System.Resources.ResourceManager ResourceManager { | |||
@@ -47,8 +47,8 @@ namespace Shadowsocks.Properties { | |||
} | |||
/// <summary> | |||
/// 使用此强类型资源类,为所有资源查找 | |||
/// 重写当前线程的 CurrentUICulture 属性。 | |||
/// Overrides the current thread's CurrentUICulture property for all | |||
/// resource lookups using this strongly typed resource class. | |||
/// </summary> | |||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] | |||
internal static global::System.Globalization.CultureInfo Culture { | |||
@@ -61,7 +61,17 @@ namespace Shadowsocks.Properties { | |||
} | |||
/// <summary> | |||
/// 查找 System.Byte[] 类型的本地化资源。 | |||
/// Looks up a localized resource of type System.Byte[]. | |||
/// </summary> | |||
internal static byte[] abp_js { | |||
get { | |||
object obj = ResourceManager.GetObject("abp_js", resourceCulture); | |||
return ((byte[])(obj)); | |||
} | |||
} | |||
/// <summary> | |||
/// Looks up a localized resource of type System.Byte[]. | |||
/// </summary> | |||
internal static byte[] builtin_txt { | |||
get { | |||
@@ -71,7 +81,7 @@ namespace Shadowsocks.Properties { | |||
} | |||
/// <summary> | |||
/// 查找类似 Shadowsocks=Shadowsocks | |||
/// Looks up a localized string similar to Shadowsocks=Shadowsocks | |||
///Enable=启用代理 | |||
///Mode=代理模式 | |||
///PAC=PAC 模式 | |||
@@ -101,7 +111,7 @@ namespace Shadowsocks.Properties { | |||
///QRCode=二维码 | |||
///Shadowsocks Error: {0}=Shadowsocks 错误: {0} | |||
///Port already in use=端口已被占用 | |||
///Il [字符串的其余部分被截断]"; 的本地化字符串。 | |||
///Il [rest of string was truncated]";. | |||
/// </summary> | |||
internal static string cn { | |||
get { | |||
@@ -110,7 +120,7 @@ namespace Shadowsocks.Properties { | |||
} | |||
/// <summary> | |||
/// 查找 System.Byte[] 类型的本地化资源。 | |||
/// Looks up a localized resource of type System.Byte[]. | |||
/// </summary> | |||
internal static byte[] libsscrypto_dll { | |||
get { | |||
@@ -120,7 +130,7 @@ namespace Shadowsocks.Properties { | |||
} | |||
/// <summary> | |||
/// 查找类似 proxyAddress = "__POLIPO_BIND_IP__" | |||
/// Looks up a localized string similar to proxyAddress = "__POLIPO_BIND_IP__" | |||
/// | |||
///socksParentProxy = "127.0.0.1:__SOCKS_PORT__" | |||
///socksProxyType = socks5 | |||
@@ -128,7 +138,7 @@ namespace Shadowsocks.Properties { | |||
///localDocumentRoot = "" | |||
/// | |||
///allowedPorts = 1-65535 | |||
///tunnelAllowedPorts = 1-65535 的本地化字符串。 | |||
///tunnelAllowedPorts = 1-65535. | |||
/// </summary> | |||
internal static string polipo_config { | |||
get { | |||
@@ -137,7 +147,7 @@ namespace Shadowsocks.Properties { | |||
} | |||
/// <summary> | |||
/// 查找 System.Byte[] 类型的本地化资源。 | |||
/// Looks up a localized resource of type System.Byte[]. | |||
/// </summary> | |||
internal static byte[] polipo_exe { | |||
get { | |||
@@ -147,7 +157,7 @@ namespace Shadowsocks.Properties { | |||
} | |||
/// <summary> | |||
/// 查找 System.Byte[] 类型的本地化资源。 | |||
/// Looks up a localized resource of type System.Byte[]. | |||
/// </summary> | |||
internal static byte[] proxy_pac_txt { | |||
get { | |||
@@ -157,7 +167,7 @@ namespace Shadowsocks.Properties { | |||
} | |||
/// <summary> | |||
/// 查找 System.Drawing.Bitmap 类型的本地化资源。 | |||
/// Looks up a localized resource of type System.Drawing.Bitmap. | |||
/// </summary> | |||
internal static System.Drawing.Bitmap ss16 { | |||
get { | |||
@@ -167,7 +177,7 @@ namespace Shadowsocks.Properties { | |||
} | |||
/// <summary> | |||
/// 查找 System.Drawing.Bitmap 类型的本地化资源。 | |||
/// Looks up a localized resource of type System.Drawing.Bitmap. | |||
/// </summary> | |||
internal static System.Drawing.Bitmap ss20 { | |||
get { | |||
@@ -177,7 +187,7 @@ namespace Shadowsocks.Properties { | |||
} | |||
/// <summary> | |||
/// 查找 System.Drawing.Bitmap 类型的本地化资源。 | |||
/// Looks up a localized resource of type System.Drawing.Bitmap. | |||
/// </summary> | |||
internal static System.Drawing.Bitmap ss24 { | |||
get { | |||
@@ -187,7 +197,7 @@ namespace Shadowsocks.Properties { | |||
} | |||
/// <summary> | |||
/// 查找 System.Drawing.Bitmap 类型的本地化资源。 | |||
/// Looks up a localized resource of type System.Drawing.Bitmap. | |||
/// </summary> | |||
internal static System.Drawing.Bitmap ssw128 { | |||
get { | |||
@@ -197,7 +207,7 @@ namespace Shadowsocks.Properties { | |||
} | |||
/// <summary> | |||
/// 查找 System.Byte[] 类型的本地化资源。 | |||
/// Looks up a localized resource of type System.Byte[]. | |||
/// </summary> | |||
internal static byte[] tld_txt { | |||
get { | |||
@@ -118,6 +118,9 @@ | |||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||
</resheader> | |||
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> | |||
<data name="abp_js" type="System.Resources.ResXFileRef, System.Windows.Forms"> | |||
<value>..\Data\abp.js.gz;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||
</data> | |||
<data name="builtin_txt" type="System.Resources.ResXFileRef, System.Windows.Forms"> | |||
<value>..\Data\builtin.txt.gz;System.Byte[], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||
</data> | |||
@@ -45,6 +45,8 @@ namespace Shadowsocks.View | |||
controller.ShareOverLANStatusChanged += controller_ShareOverLANStatusChanged; | |||
controller.EnableGlobalChanged += controller_EnableGlobalChanged; | |||
controller.Errored += controller_Errored; | |||
controller.UpdatePACFromGFWListCompleted += controller_UpdatePACFromGFWListCompleted; | |||
controller.UpdatePACFromGFWListError += controller_UpdatePACFromGFWListError; | |||
_notifyIcon = new NotifyIcon(); | |||
UpdateTrayIcon(); | |||
@@ -138,6 +140,7 @@ namespace Shadowsocks.View | |||
this.AutoStartupItem = CreateMenuItem("Start on Boot", new EventHandler(this.AutoStartupItem_Click)), | |||
this.ShareOverLANItem = CreateMenuItem("Share over LAN", new EventHandler(this.ShareOverLANItem_Click)), | |||
CreateMenuItem("Edit PAC File...", new EventHandler(this.EditPACFileItem_Click)), | |||
CreateMenuItem("Update PAC File via gfwlist...", new EventHandler(this.UpdatePACFromGFWListItem_Click)), | |||
new MenuItem("-"), | |||
CreateMenuItem("Show QRCode...", new EventHandler(this.QRCodeItem_Click)), | |||
CreateMenuItem("Show Logs...", new EventHandler(this.ShowLogItem_Click)), | |||
@@ -176,6 +179,23 @@ namespace Shadowsocks.View | |||
System.Diagnostics.Process.Start("explorer.exe", argument); | |||
} | |||
void controller_UpdatePACFromGFWListError(object sender, System.IO.ErrorEventArgs e) | |||
{ | |||
_notifyIcon.BalloonTipTitle = I18N.GetString("Update PAC File via gfwlist..."); | |||
_notifyIcon.BalloonTipText = I18N.GetString("Update PAC file failed"); | |||
_notifyIcon.BalloonTipIcon = ToolTipIcon.Info; | |||
_notifyIcon.ShowBalloonTip(5000); | |||
Logging.LogUsefulException(e.GetException()); | |||
} | |||
void controller_UpdatePACFromGFWListCompleted(object sender, EventArgs e) | |||
{ | |||
_notifyIcon.BalloonTipTitle = I18N.GetString("Update PAC File via gfwlist..."); | |||
_notifyIcon.BalloonTipText = I18N.GetString("Update PAC file succeed"); | |||
_notifyIcon.BalloonTipIcon = ToolTipIcon.Info; | |||
_notifyIcon.ShowBalloonTip(5000); | |||
} | |||
void updateChecker_NewVersionFound(object sender, EventArgs e) | |||
{ | |||
_notifyIcon.BalloonTipTitle = String.Format(I18N.GetString("Shadowsocks {0} Update Found"), updateChecker.LatestVersionNumber); | |||
@@ -311,6 +331,15 @@ namespace Shadowsocks.View | |||
controller.TouchPACFile(); | |||
} | |||
private void UpdatePACFromGFWListItem_Click(object sender, EventArgs e) | |||
{ | |||
_notifyIcon.BalloonTipTitle = I18N.GetString("Shadowsocks") + " " + UpdateChecker.Version; | |||
_notifyIcon.BalloonTipText = I18N.GetString("Update PAC File via gfwlist..."); | |||
_notifyIcon.BalloonTipIcon = ToolTipIcon.Info; | |||
_notifyIcon.ShowBalloonTip(5000); | |||
controller.UpdatePACFromGFWList(); | |||
} | |||
private void AServerItem_Click(object sender, EventArgs e) | |||
{ | |||
MenuItem item = (MenuItem)sender; | |||
@@ -143,6 +143,7 @@ | |||
<None Include="app.manifest"> | |||
<SubType>Designer</SubType> | |||
</None> | |||
<None Include="Data\abp.js.gz" /> | |||
<None Include="Data\builtin.txt.gz" /> | |||
<None Include="Data\libsscrypto.dll.gz" /> | |||
<None Include="Data\polipo.exe.gz" /> | |||