@@ -1,3 +1,4 @@ | |||
/.vs/ | |||
Backup/ | |||
bin/ | |||
obj/ | |||
@@ -5,3 +6,9 @@ shadowsocks-csharp/shadowsocks-csharp.csproj.user | |||
TestResults | |||
*.suo | |||
shadowsocks-csharp/3rd/* | |||
!shadowsocks-csharp/3rd/zxing/ | |||
!shadowsocks-csharp/3rd/SimpleJson.cs | |||
packages/* | |||
shadowsocks-csharp.sln.DotSettings.user |
@@ -1,3 +1,9 @@ | |||
2.5.8 2015-09-20 | |||
- Update GFWList url | |||
2.5.7 2015-09-19 | |||
- Fix repeated IV | |||
2.5.6 2015-08-19 | |||
- Add portable mode. Create shadowsocks_portable_mode.txt to use it | |||
- Support server reorder | |||
@@ -75,8 +75,8 @@ Visual Studio 2015 is required. | |||
GPLv3 | |||
[Appveyor]: https://ci.appveyor.com/project/clowwindy/shadowsocks-csharp | |||
[Build Status]: https://ci.appveyor.com/api/projects/status/gknc8l1lxy423ehv/branch/master | |||
[Appveyor]: https://ci.appveyor.com/project/icylogic/shadowsocks-windows-l9mwe | |||
[Build Status]: https://ci.appveyor.com/api/projects/status/ytllr9yjkbpc2tu2/branch/master | |||
[latest release]: https://github.com/shadowsocks/shadowsocks-csharp/releases | |||
[GFWList]: https://github.com/gfwlist/gfwlist | |||
[Servers]: https://github.com/shadowsocks/shadowsocks/wiki/Ports-and-Clients#linux--server-side | |||
@@ -0,0 +1,6 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<configuration> | |||
<config> | |||
<add key="repositoryPath" value="shadowsocks-csharp\3rd" /> | |||
</config> | |||
</configuration> |
@@ -1,8 +1,6 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.IO; | |||
using System.IO.Compression; | |||
using System.Text; | |||
namespace Shadowsocks.Controller | |||
{ | |||
@@ -12,9 +10,7 @@ namespace Shadowsocks.Controller | |||
{ | |||
try | |||
{ | |||
System.IO.FileStream _FileStream = | |||
new System.IO.FileStream(fileName, System.IO.FileMode.Create, | |||
System.IO.FileAccess.Write); | |||
FileStream _FileStream = new FileStream(fileName, FileMode.Create, FileAccess.Write); | |||
_FileStream.Write(content, 0, content.Length); | |||
_FileStream.Close(); | |||
return true; | |||
@@ -31,7 +27,7 @@ namespace Shadowsocks.Controller | |||
{ | |||
FileStream destinationFile = File.Create(fileName); | |||
// Because the uncompressed size of the file is unknown, | |||
// Because the uncompressed size of the file is unknown, | |||
// we are using an arbitrary buffer size. | |||
byte[] buffer = new byte[4096]; | |||
int n; | |||
@@ -39,17 +35,13 @@ namespace Shadowsocks.Controller | |||
using (GZipStream input = new GZipStream(new MemoryStream(content), | |||
CompressionMode.Decompress, false)) | |||
{ | |||
while (true) | |||
while ((n = input.Read(buffer, 0, buffer.Length)) > 0) | |||
{ | |||
n = input.Read(buffer, 0, buffer.Length); | |||
if (n == 0) | |||
{ | |||
break; | |||
} | |||
destinationFile.Write(buffer, 0, n); | |||
} | |||
} | |||
destinationFile.Close(); | |||
} | |||
} | |||
} |
@@ -1,23 +1,23 @@ | |||
using Shadowsocks.Util; | |||
using System; | |||
using System.Collections.Generic; | |||
using System; | |||
using System.IO; | |||
using System.Net.Sockets; | |||
using System.Text; | |||
using System.Net; | |||
using Shadowsocks.Util; | |||
namespace Shadowsocks.Controller | |||
{ | |||
public class Logging | |||
{ | |||
public static string LogFile; | |||
public static string LogFilePath; | |||
public static bool OpenLogFile() | |||
{ | |||
try | |||
{ | |||
string temppath = Utils.GetTempPath(); | |||
LogFile = Path.Combine(temppath, "shadowsocks.log"); | |||
FileStream fs = new FileStream(LogFile, FileMode.Append); | |||
LogFilePath = Utils.GetTempPath("shadowsocks.log"); | |||
FileStream fs = new FileStream(LogFilePath, FileMode.Append); | |||
StreamWriterWithTimestamp sw = new StreamWriterWithTimestamp(fs); | |||
sw.AutoFlush = true; | |||
Console.SetOut(sw); | |||
@@ -32,11 +32,46 @@ namespace Shadowsocks.Controller | |||
} | |||
} | |||
private static void WriteToLogFile(object o) | |||
{ | |||
Console.WriteLine(o); | |||
} | |||
public static void Error(object o) | |||
{ | |||
WriteToLogFile("[E] " + o); | |||
} | |||
public static void Info(object o) | |||
{ | |||
WriteToLogFile(o); | |||
} | |||
public static void Debug(object o) | |||
{ | |||
#if DEBUG | |||
WriteToLogFile("[D] " + o); | |||
#endif | |||
} | |||
public static void Debug(EndPoint local, EndPoint remote, int len, string header = null, string tailer = null) | |||
{ | |||
#if DEBUG | |||
Console.WriteLine(o); | |||
if (header == null && tailer == null) | |||
Debug($"{local} => {remote} (size={len})"); | |||
else if (header == null && tailer != null) | |||
Debug($"{local} => {remote} (size={len}), {tailer}"); | |||
else if (header != null && tailer == null) | |||
Debug($"{header}: {local} => {remote} (size={len})"); | |||
else | |||
Debug($"{header}: {local} => {remote} (size={len}), {tailer}"); | |||
#endif | |||
} | |||
public static void Debug(Socket sock, int len, string header = null, string tailer = null) | |||
{ | |||
#if DEBUG | |||
Debug(sock.LocalEndPoint, sock.RemoteEndPoint, len, header, tailer); | |||
#endif | |||
} | |||
@@ -57,11 +92,19 @@ namespace Shadowsocks.Controller | |||
} | |||
else if (se.SocketErrorCode == SocketError.NotConnected) | |||
{ | |||
// close when not connected | |||
// The application tried to send or receive data, and the System.Net.Sockets.Socket is not connected. | |||
} | |||
else if (se.SocketErrorCode == SocketError.HostUnreachable) | |||
{ | |||
// There is no network route to the specified host. | |||
} | |||
else if (se.SocketErrorCode == SocketError.TimedOut) | |||
{ | |||
// The connection attempt timed out, or the connected host has failed to respond. | |||
} | |||
else | |||
{ | |||
Console.WriteLine(e); | |||
Info(e); | |||
} | |||
} | |||
else if (e is ObjectDisposedException) | |||
@@ -69,7 +112,7 @@ namespace Shadowsocks.Controller | |||
} | |||
else | |||
{ | |||
Console.WriteLine(e); | |||
Info(e); | |||
} | |||
} | |||
} | |||
@@ -1,50 +1,75 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Globalization; | |||
using System.IO; | |||
using System.Linq; | |||
using System.Net; | |||
using System.Net.Http; | |||
using System.Net.NetworkInformation; | |||
using System.Net.Sockets; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using Newtonsoft.Json; | |||
using Newtonsoft.Json.Linq; | |||
using Shadowsocks.Model; | |||
using System.Reflection; | |||
using Shadowsocks.Util; | |||
using Shadowsocks.Util; | |||
namespace Shadowsocks.Controller | |||
{ | |||
class AvailabilityStatistics | |||
using DataUnit = KeyValuePair<string, string>; | |||
using DataList = List<KeyValuePair<string, string>>; | |||
using Statistics = Dictionary<string, List<AvailabilityStatistics.RawStatisticsData>>; | |||
public class AvailabilityStatistics | |||
{ | |||
private static readonly string StatisticsFilesName = "shadowsocks.availability.csv"; | |||
private static readonly string Delimiter = ","; | |||
private static readonly int Timeout = 500; | |||
private static readonly int Repeat = 4; //repeat times every evaluation | |||
private static readonly int Interval = 10 * 60 * 1000; //evaluate proxies every 15 minutes | |||
private Timer timer = null; | |||
private State state = null; | |||
private List<Server> servers; | |||
public static readonly string DateTimePattern = "yyyy-MM-dd HH:mm:ss"; | |||
private const string StatisticsFilesName = "shadowsocks.availability.csv"; | |||
private const string Delimiter = ","; | |||
private const int Timeout = 500; | |||
private const int DelayBeforeStart = 1000; | |||
public Statistics RawStatistics { get; private set; } | |||
public Statistics FilteredStatistics { get; private set; } | |||
public static readonly DateTime UnknownDateTime = new DateTime(1970, 1, 1); | |||
private int Repeat => _config.RepeatTimesNum; | |||
private const int RetryInterval = 2 * 60 * 1000; //retry 2 minutes after failed | |||
private int Interval => (int)TimeSpan.FromMinutes(_config.DataCollectionMinutes).TotalMilliseconds; | |||
private Timer _timer; | |||
private State _state; | |||
private List<Server> _servers; | |||
private StatisticsStrategyConfiguration _config; | |||
public static string AvailabilityStatisticsFile; | |||
//static constructor to initialize every public static fields before refereced | |||
static AvailabilityStatistics() | |||
{ | |||
string temppath = Utils.GetTempPath(); | |||
AvailabilityStatisticsFile = Path.Combine(temppath, StatisticsFilesName); | |||
AvailabilityStatisticsFile = Utils.GetTempPath(StatisticsFilesName); | |||
} | |||
public bool Set(bool enabled) | |||
public AvailabilityStatistics(Configuration config, StatisticsStrategyConfiguration statisticsConfig) | |||
{ | |||
UpdateConfiguration(config, statisticsConfig); | |||
} | |||
public bool Set(StatisticsStrategyConfiguration config) | |||
{ | |||
_config = config; | |||
try | |||
{ | |||
if (enabled) | |||
if (config.StatisticsEnabled) | |||
{ | |||
if (timer?.Change(0, Interval) == null) | |||
if (_timer?.Change(DelayBeforeStart, Interval) == null) | |||
{ | |||
state = new State(); | |||
timer = new Timer(Evaluate, state, 0, Interval); | |||
_state = new State(); | |||
_timer = new Timer(Run, _state, DelayBeforeStart, Interval); | |||
} | |||
} | |||
else | |||
{ | |||
timer?.Dispose(); | |||
_timer?.Dispose(); | |||
} | |||
return true; | |||
} | |||
@@ -55,63 +80,262 @@ namespace Shadowsocks.Controller | |||
} | |||
} | |||
private void Evaluate(object obj) | |||
//hardcode | |||
//TODO: backup reliable isp&geolocation provider or a local database is required | |||
public static async Task<DataList> GetGeolocationAndIsp() | |||
{ | |||
Ping ping = new Ping(); | |||
State state = (State) obj; | |||
foreach (var server in servers) | |||
Logging.Debug("Retrive information of geolocation and isp"); | |||
const string API = "http://ip-api.com/json"; | |||
const string alternativeAPI = "http://www.telize.com/geoip"; //must be comptible with current API | |||
var result = await GetInfoFromAPI(API); | |||
if (result != null) return result; | |||
result = await GetInfoFromAPI(alternativeAPI); | |||
if (result != null) return result; | |||
return new DataList | |||
{ | |||
new DataUnit(State.Geolocation, State.Unknown), | |||
new DataUnit(State.ISP, State.Unknown) | |||
}; | |||
} | |||
private static async Task<DataList> GetInfoFromAPI(string API) | |||
{ | |||
string jsonString; | |||
try | |||
{ | |||
jsonString = await new HttpClient().GetStringAsync(API); | |||
} | |||
catch (HttpRequestException e) | |||
{ | |||
Logging.LogUsefulException(e); | |||
return null; | |||
} | |||
JObject obj; | |||
try | |||
{ | |||
obj = JObject.Parse(jsonString); | |||
} | |||
catch (JsonReaderException) | |||
{ | |||
Logging.Debug("eveluating " + server.FriendlyName()); | |||
foreach (var _ in Enumerable.Range(0, Repeat)) | |||
return null; | |||
} | |||
string country = (string)obj["country"]; | |||
string city = (string)obj["city"]; | |||
string isp = (string)obj["isp"]; | |||
if (country == null || city == null || isp == null) return null; | |||
return new DataList { | |||
new DataUnit(State.Geolocation, $"\"{country} {city}\""), | |||
new DataUnit(State.ISP, $"\"{isp}\"") | |||
}; | |||
} | |||
private async Task<List<DataList>> ICMPTest(Server server) | |||
{ | |||
Logging.Debug("Ping " + server.FriendlyName()); | |||
if (server.server == "") return null; | |||
var ret = new List<DataList>(); | |||
try { | |||
var IP = Dns.GetHostAddresses(server.server).First(ip => (ip.AddressFamily == AddressFamily.InterNetwork || ip.AddressFamily == AddressFamily.InterNetworkV6)); | |||
var ping = new Ping(); | |||
foreach (var timestamp in Enumerable.Range(0, Repeat).Select(_ => DateTime.Now.ToString(DateTimePattern))) | |||
{ | |||
//TODO: do simple analyze of data to provide friendly message, like package loss. | |||
string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); | |||
//ICMP echo. we can also set options and special bytes | |||
//seems no need to use SendPingAsync | |||
try | |||
{ | |||
PingReply reply = ping.Send(server.server, Timeout); | |||
state.data = new List<KeyValuePair<string, string>>(); | |||
state.data.Add(new KeyValuePair<string, string>("Timestamp", timestamp)); | |||
state.data.Add(new KeyValuePair<string, string>("Server", server.FriendlyName())); | |||
state.data.Add(new KeyValuePair<string, string>("Status", reply.Status.ToString())); | |||
state.data.Add(new KeyValuePair<string, string>("RoundtripTime", reply.RoundtripTime.ToString())); | |||
//state.data.Add(new KeyValuePair<string, string>("data", reply.Buffer.ToString())); // The data of reply | |||
Append(state.data); | |||
var reply = await ping.SendTaskAsync(IP, Timeout); | |||
ret.Add(new List<KeyValuePair<string, string>> | |||
{ | |||
new KeyValuePair<string, string>("Timestamp", timestamp), | |||
new KeyValuePair<string, string>("Server", server.FriendlyName()), | |||
new KeyValuePair<string, string>("Status", reply?.Status.ToString()), | |||
new KeyValuePair<string, string>("RoundtripTime", reply?.RoundtripTime.ToString()) | |||
//new KeyValuePair<string, string>("data", reply.Buffer.ToString()); // The data of reply | |||
}); | |||
Thread.Sleep(Timeout + new Random().Next() % Timeout); | |||
//Do ICMPTest in a random frequency | |||
} | |||
catch (Exception e) | |||
{ | |||
Logging.Error($"An exception occured while eveluating {server.FriendlyName()}"); | |||
Logging.LogUsefulException(e); | |||
} | |||
} | |||
}catch(Exception e) | |||
{ | |||
Logging.Error($"An exception occured while eveluating {server.FriendlyName()}"); | |||
Logging.LogUsefulException(e); | |||
} | |||
return ret; | |||
} | |||
private void Run(object obj) | |||
{ | |||
LoadRawStatistics(); | |||
FilterRawStatistics(); | |||
evaluate(); | |||
} | |||
private async void evaluate() | |||
{ | |||
var geolocationAndIsp = GetGeolocationAndIsp(); | |||
foreach (var dataLists in await TaskEx.WhenAll(_servers.Select(ICMPTest))) | |||
{ | |||
if (dataLists == null) continue; | |||
foreach (var dataList in dataLists.Where(dataList => dataList != null)) | |||
{ | |||
await geolocationAndIsp; | |||
Append(dataList, geolocationAndIsp.Result); | |||
} | |||
} | |||
} | |||
private static void Append(List<KeyValuePair<string, string>> data) | |||
private static void Append(DataList dataList, IEnumerable<DataUnit> extra) | |||
{ | |||
string dataLine = string.Join(Delimiter, data.Select(kv => kv.Value).ToArray()); | |||
var data = dataList.Concat(extra); | |||
var dataLine = string.Join(Delimiter, data.Select(kv => kv.Value).ToArray()); | |||
string[] lines; | |||
if (!File.Exists(AvailabilityStatisticsFile)) | |||
{ | |||
string headerLine = string.Join(Delimiter, data.Select(kv => kv.Key).ToArray()); | |||
lines = new string[] { headerLine, dataLine }; | |||
var headerLine = string.Join(Delimiter, data.Select(kv => kv.Key).ToArray()); | |||
lines = new[] { headerLine, dataLine }; | |||
} | |||
else | |||
{ | |||
lines = new string[] { dataLine }; | |||
lines = new[] { dataLine }; | |||
} | |||
File.AppendAllLines(AvailabilityStatisticsFile, lines); | |||
try | |||
{ | |||
File.AppendAllLines(AvailabilityStatisticsFile, lines); | |||
} | |||
catch (IOException e) | |||
{ | |||
Logging.LogUsefulException(e); | |||
} | |||
} | |||
internal void UpdateConfiguration(Configuration config, StatisticsStrategyConfiguration statisticsConfig) | |||
{ | |||
Set(statisticsConfig); | |||
_servers = config.configs; | |||
} | |||
private async void FilterRawStatistics() | |||
{ | |||
if (RawStatistics == null) return; | |||
if (FilteredStatistics == null) | |||
{ | |||
FilteredStatistics = new Statistics(); | |||
} | |||
foreach (IEnumerable<RawStatisticsData> rawData in RawStatistics.Values) | |||
{ | |||
var filteredData = rawData; | |||
if (_config.ByIsp) | |||
{ | |||
var current = await GetGeolocationAndIsp(); | |||
filteredData = | |||
filteredData.Where( | |||
data => | |||
data.Geolocation == current[0].Value || | |||
data.Geolocation == State.Unknown); | |||
filteredData = | |||
filteredData.Where( | |||
data => data.ISP == current[1].Value || data.ISP == State.Unknown); | |||
if (filteredData.LongCount() == 0) return; | |||
} | |||
if (_config.ByHourOfDay) | |||
{ | |||
var currentHour = DateTime.Now.Hour; | |||
filteredData = filteredData.Where(data => | |||
data.Timestamp != UnknownDateTime && data.Timestamp.Hour.Equals(currentHour) | |||
); | |||
if (filteredData.LongCount() == 0) return; | |||
} | |||
var dataList = filteredData as List<RawStatisticsData> ?? filteredData.ToList(); | |||
var serverName = dataList[0].ServerName; | |||
FilteredStatistics[serverName] = dataList; | |||
} | |||
} | |||
private void LoadRawStatistics() | |||
{ | |||
try | |||
{ | |||
var path = AvailabilityStatisticsFile; | |||
Logging.Debug($"loading statistics from {path}"); | |||
if (!File.Exists(path)) | |||
{ | |||
try { | |||
using (FileStream fs = File.Create(path)) | |||
{ | |||
//do nothing | |||
} | |||
}catch(Exception e) | |||
{ | |||
Logging.LogUsefulException(e); | |||
} | |||
if (!File.Exists(path)) { | |||
Console.WriteLine($"statistics file does not exist, try to reload {RetryInterval / 60 / 1000} minutes later"); | |||
_timer.Change(RetryInterval, Interval); | |||
return; | |||
} | |||
} | |||
RawStatistics = (from l in File.ReadAllLines(path).Skip(1) | |||
let strings = l.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries) | |||
let rawData = new RawStatisticsData | |||
{ | |||
Timestamp = ParseExactOrUnknown(strings[0]), | |||
ServerName = strings[1], | |||
ICMPStatus = strings[2], | |||
RoundtripTime = int.Parse(strings[3]), | |||
Geolocation = 5 > strings.Length ? | |||
null | |||
: strings[4], | |||
ISP = 6 > strings.Length ? null : strings[5] | |||
} | |||
group rawData by rawData.ServerName into server | |||
select new | |||
{ | |||
ServerName = server.Key, | |||
data = server.ToList() | |||
}).ToDictionary(server => server.ServerName, server => server.data); | |||
} | |||
catch (Exception e) | |||
{ | |||
Logging.LogUsefulException(e); | |||
} | |||
} | |||
private DateTime ParseExactOrUnknown(string str) | |||
{ | |||
DateTime dateTime; | |||
return !DateTime.TryParseExact(str, DateTimePattern, null, DateTimeStyles.None, out dateTime) ? UnknownDateTime : dateTime; | |||
} | |||
public class State | |||
{ | |||
public DataList dataList = new DataList(); | |||
public const string Geolocation = "Geolocation"; | |||
public const string ISP = "ISP"; | |||
public const string Unknown = "Unknown"; | |||
} | |||
internal void UpdateConfiguration(Configuration _config) | |||
public class RawStatisticsData | |||
{ | |||
Set(_config.availabilityStatistics); | |||
servers = _config.configs; | |||
public DateTime Timestamp; | |||
public string ServerName; | |||
public string ICMPStatus; | |||
public int RoundtripTime; | |||
public string Geolocation; | |||
public string ISP; | |||
} | |||
private class State | |||
public class StatisticsData | |||
{ | |||
public List<KeyValuePair<string, string>> data = new List<KeyValuePair<string, string>>(); | |||
public float PackageLoss; | |||
public int AverageResponse; | |||
public int MinResponse; | |||
public int MaxResponse; | |||
} | |||
} | |||
} |
@@ -1,22 +1,20 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Text; | |||
using System.Net; | |||
using System.IO; | |||
using System.Net; | |||
using System.Text; | |||
using Newtonsoft.Json; | |||
using Shadowsocks.Model; | |||
using Shadowsocks.Properties; | |||
using SimpleJson; | |||
using Shadowsocks.Util; | |||
using Shadowsocks.Model; | |||
namespace Shadowsocks.Controller | |||
{ | |||
public class GFWListUpdater | |||
{ | |||
private const string GFWLIST_URL = "https://autoproxy-gfwlist.googlecode.com/svn/trunk/gfwlist.txt"; | |||
private static string PAC_FILE = PACServer.PAC_FILE; | |||
private static string USER_RULE_FILE = PACServer.USER_RULE_FILE; | |||
private const string GFWLIST_URL = "https://raw.githubusercontent.com/gfwlist/gfwlist/master/gfwlist.txt"; | |||
public event EventHandler<ResultEventArgs> UpdateCompleted; | |||
@@ -36,30 +34,39 @@ namespace Shadowsocks.Controller | |||
{ | |||
try | |||
{ | |||
File.WriteAllText(Utils.GetTempPath("gfwlist.txt"), e.Result, Encoding.UTF8); | |||
List<string> lines = ParseResult(e.Result); | |||
if (File.Exists(USER_RULE_FILE)) | |||
if (File.Exists(PACServer.USER_RULE_FILE)) | |||
{ | |||
string local = File.ReadAllText(USER_RULE_FILE, Encoding.UTF8); | |||
string local = File.ReadAllText(PACServer.USER_RULE_FILE, Encoding.UTF8); | |||
string[] rules = local.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); | |||
foreach(string rule in rules) | |||
foreach (string rule in rules) | |||
{ | |||
if (rule.StartsWith("!") || rule.StartsWith("[")) | |||
continue; | |||
lines.Add(rule); | |||
} | |||
} | |||
string abpContent = Utils.UnGzip(Resources.abp_js); | |||
abpContent = abpContent.Replace("__RULES__", SimpleJson.SimpleJson.SerializeObject(lines)); | |||
if (File.Exists(PAC_FILE)) | |||
string abpContent; | |||
if (File.Exists(PACServer.USER_ABP_FILE)) | |||
{ | |||
abpContent = File.ReadAllText(PACServer.USER_ABP_FILE, Encoding.UTF8); | |||
} | |||
else | |||
{ | |||
abpContent = Utils.UnGzip(Resources.abp_js); | |||
} | |||
abpContent = abpContent.Replace("__RULES__", JsonConvert.SerializeObject(lines, Formatting.Indented)); | |||
if (File.Exists(PACServer.PAC_FILE)) | |||
{ | |||
string original = File.ReadAllText(PAC_FILE, Encoding.UTF8); | |||
string original = File.ReadAllText(PACServer.PAC_FILE, Encoding.UTF8); | |||
if (original == abpContent) | |||
{ | |||
UpdateCompleted(this, new ResultEventArgs(false)); | |||
return; | |||
} | |||
} | |||
File.WriteAllText(PAC_FILE, abpContent, Encoding.UTF8); | |||
File.WriteAllText(PACServer.PAC_FILE, abpContent, Encoding.UTF8); | |||
if (UpdateCompleted != null) | |||
{ | |||
UpdateCompleted(this, new ResultEventArgs(true)); | |||
@@ -82,7 +89,7 @@ namespace Shadowsocks.Controller | |||
http.DownloadStringAsync(new Uri(GFWLIST_URL)); | |||
} | |||
public List<string> ParseResult(string response) | |||
public static List<string> ParseResult(string response) | |||
{ | |||
byte[] bytes = Convert.FromBase64String(response); | |||
string content = Encoding.ASCII.GetString(bytes); | |||
@@ -97,4 +104,4 @@ namespace Shadowsocks.Controller | |||
return valid_lines; | |||
} | |||
} | |||
} | |||
} |
@@ -1,10 +1,10 @@ | |||
using Shadowsocks.Model; | |||
using System; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Net; | |||
using System.Net.NetworkInformation; | |||
using System.Net.Sockets; | |||
using System.Text; | |||
using Shadowsocks.Model; | |||
namespace Shadowsocks.Controller | |||
{ | |||
@@ -78,10 +78,8 @@ namespace Shadowsocks.Controller | |||
_tcpSocket.Listen(1024); | |||
// Start an asynchronous socket to listen for connections. | |||
Console.WriteLine("Shadowsocks started"); | |||
_tcpSocket.BeginAccept( | |||
new AsyncCallback(AcceptCallback), | |||
_tcpSocket); | |||
Logging.Info("Shadowsocks started"); | |||
_tcpSocket.BeginAccept(new AsyncCallback(AcceptCallback), _tcpSocket); | |||
UDPState udpState = new UDPState(); | |||
_udpSocket.BeginReceiveFrom(udpState.buffer, 0, udpState.buffer.Length, 0, ref udpState.remoteEndPoint, new AsyncCallback(RecvFromCallback), udpState); | |||
} | |||
@@ -163,7 +161,7 @@ namespace Shadowsocks.Controller | |||
} | |||
catch (Exception e) | |||
{ | |||
Console.WriteLine(e); | |||
Logging.LogUsefulException(e); | |||
} | |||
finally | |||
{ | |||
@@ -208,7 +206,7 @@ namespace Shadowsocks.Controller | |||
} | |||
catch (Exception e) | |||
{ | |||
Console.WriteLine(e); | |||
Logging.LogUsefulException(e); | |||
conn.Close(); | |||
} | |||
} | |||
@@ -1,31 +1,33 @@ | |||
using Shadowsocks.Model; | |||
using Shadowsocks.Properties; | |||
using Shadowsocks.Util; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Diagnostics; | |||
using System; | |||
using System.Collections; | |||
using System.IO; | |||
using System.IO.Compression; | |||
using System.Net; | |||
using System.Net.Sockets; | |||
using System.Text; | |||
using Shadowsocks.Model; | |||
using Shadowsocks.Properties; | |||
using Shadowsocks.Util; | |||
namespace Shadowsocks.Controller | |||
{ | |||
class PACServer : Listener.Service | |||
{ | |||
public static string PAC_FILE = "pac.txt"; | |||
public static string USER_RULE_FILE = "user-rule.txt"; | |||
public static readonly string PAC_FILE = "pac.txt"; | |||
public static readonly string USER_RULE_FILE = "user-rule.txt"; | |||
public static readonly string USER_ABP_FILE = "abp.txt"; | |||
FileSystemWatcher watcher; | |||
FileSystemWatcher PACFileWatcher; | |||
FileSystemWatcher UserRuleFileWatcher; | |||
private Configuration _config; | |||
public event EventHandler PACFileChanged; | |||
public event EventHandler UserRuleFileChanged; | |||
public PACServer() | |||
{ | |||
this.WatchPacFile(); | |||
this.WatchUserRuleFile(); | |||
} | |||
public void UpdateConfiguration(Configuration config) | |||
@@ -46,7 +48,7 @@ namespace Shadowsocks.Controller | |||
bool hostMatch = false, pathMatch = false, useSocks = false; | |||
foreach (string line in lines) | |||
{ | |||
string[] kv = line.Split(new char[]{':'}, 2); | |||
string[] kv = line.Split(new char[] { ':' }, 2); | |||
if (kv.Length == 2) | |||
{ | |||
if (kv[0] == "Host") | |||
@@ -56,14 +58,14 @@ namespace Shadowsocks.Controller | |||
hostMatch = true; | |||
} | |||
} | |||
else if (kv[0] == "User-Agent") | |||
{ | |||
// we need to drop connections when changing servers | |||
/* if (kv[1].IndexOf("Chrome") >= 0) | |||
{ | |||
useSocks = true; | |||
} */ | |||
} | |||
//else if (kv[0] == "User-Agent") | |||
//{ | |||
// // we need to drop connections when changing servers | |||
// if (kv[1].IndexOf("Chrome") >= 0) | |||
// { | |||
// useSocks = true; | |||
// } | |||
//} | |||
} | |||
else if (kv.Length == 1) | |||
{ | |||
@@ -142,14 +144,14 @@ Content-Type: application/x-ns-proxy-autoconfig | |||
Content-Length: {0} | |||
Connection: Close | |||
", System.Text.Encoding.UTF8.GetBytes(pac).Length) + pac; | |||
byte[] response = System.Text.Encoding.UTF8.GetBytes(text); | |||
", Encoding.UTF8.GetBytes(pac).Length) + pac; | |||
byte[] response = Encoding.UTF8.GetBytes(text); | |||
socket.BeginSend(response, 0, response.Length, 0, new AsyncCallback(SendCallback), socket); | |||
Util.Utils.ReleaseMemory(true); | |||
Utils.ReleaseMemory(true); | |||
} | |||
catch (Exception e) | |||
{ | |||
Console.WriteLine(e); | |||
Logging.LogUsefulException(e); | |||
socket.Close(); | |||
} | |||
} | |||
@@ -167,27 +169,78 @@ Connection: Close | |||
private void WatchPacFile() | |||
{ | |||
if (watcher != null) | |||
{ | |||
watcher.Dispose(); | |||
} | |||
watcher = new FileSystemWatcher(Directory.GetCurrentDirectory()); | |||
watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; | |||
watcher.Filter = PAC_FILE; | |||
watcher.Changed += Watcher_Changed; | |||
watcher.Created += Watcher_Changed; | |||
watcher.Deleted += Watcher_Changed; | |||
watcher.Renamed += Watcher_Changed; | |||
watcher.EnableRaisingEvents = true; | |||
if (PACFileWatcher != null) | |||
{ | |||
PACFileWatcher.Dispose(); | |||
} | |||
PACFileWatcher = new FileSystemWatcher(Directory.GetCurrentDirectory()); | |||
PACFileWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; | |||
PACFileWatcher.Filter = PAC_FILE; | |||
PACFileWatcher.Changed += PACFileWatcher_Changed; | |||
PACFileWatcher.Created += PACFileWatcher_Changed; | |||
PACFileWatcher.Deleted += PACFileWatcher_Changed; | |||
PACFileWatcher.Renamed += PACFileWatcher_Changed; | |||
PACFileWatcher.EnableRaisingEvents = true; | |||
} | |||
private void Watcher_Changed(object sender, FileSystemEventArgs e) | |||
private void WatchUserRuleFile() | |||
{ | |||
if (PACFileChanged != null) | |||
if (UserRuleFileWatcher != null) | |||
{ | |||
PACFileChanged(this, new EventArgs()); | |||
UserRuleFileWatcher.Dispose(); | |||
} | |||
UserRuleFileWatcher = new FileSystemWatcher(Directory.GetCurrentDirectory()); | |||
UserRuleFileWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; | |||
UserRuleFileWatcher.Filter = USER_RULE_FILE; | |||
UserRuleFileWatcher.Changed += UserRuleFileWatcher_Changed; | |||
UserRuleFileWatcher.Created += UserRuleFileWatcher_Changed; | |||
UserRuleFileWatcher.Deleted += UserRuleFileWatcher_Changed; | |||
UserRuleFileWatcher.Renamed += UserRuleFileWatcher_Changed; | |||
UserRuleFileWatcher.EnableRaisingEvents = true; | |||
} | |||
#region FileSystemWatcher.OnChanged() | |||
// FileSystemWatcher Changed event is raised twice | |||
// http://stackoverflow.com/questions/1764809/filesystemwatcher-changed-event-is-raised-twice | |||
private static Hashtable fileChangedTime = new Hashtable(); | |||
private void PACFileWatcher_Changed(object sender, FileSystemEventArgs e) | |||
{ | |||
string path = e.FullPath.ToString(); | |||
string currentLastWriteTime = File.GetLastWriteTime(e.FullPath).ToString(); | |||
// if there is no path info stored yet or stored path has different time of write then the one now is inspected | |||
if (!fileChangedTime.ContainsKey(path) || fileChangedTime[path].ToString() != currentLastWriteTime) | |||
{ | |||
if (PACFileChanged != null) | |||
{ | |||
Logging.Info($"Detected: PAC file '{e.Name}' was {e.ChangeType.ToString().ToLower()}."); | |||
PACFileChanged(this, new EventArgs()); | |||
} | |||
// lastly we update the last write time in the hashtable | |||
fileChangedTime[path] = currentLastWriteTime; | |||
} | |||
} | |||
private void UserRuleFileWatcher_Changed(object sender, FileSystemEventArgs e) | |||
{ | |||
string path = e.FullPath.ToString(); | |||
string currentLastWriteTime = File.GetLastWriteTime(e.FullPath).ToString(); | |||
// if there is no path info stored yet or stored path has different time of write then the one now is inspected | |||
if (!fileChangedTime.ContainsKey(path) || fileChangedTime[path].ToString() != currentLastWriteTime) | |||
{ | |||
if (UserRuleFileChanged != null) | |||
{ | |||
Logging.Info($"Detected: User Rule file '{e.Name}' was {e.ChangeType.ToString().ToLower()}."); | |||
UserRuleFileChanged(this, new EventArgs()); | |||
} | |||
// lastly we update the last write time in the hashtable | |||
fileChangedTime[path] = currentLastWriteTime; | |||
} | |||
} | |||
#endregion | |||
private string GetPACAddress(byte[] requestBuf, int length, IPEndPoint localEndPoint, bool useSocks) | |||
{ | |||
@@ -202,7 +255,7 @@ Connection: Close | |||
//} | |||
//catch (Exception e) | |||
//{ | |||
// Console.WriteLine(e); | |||
// Logging.LogUsefulException(e); | |||
//} | |||
return (useSocks ? "SOCKS5 " : "PROXY ") + localEndPoint.Address + ":" + this._config.localPort + ";"; | |||
} | |||
@@ -1,14 +1,14 @@ | |||
using Shadowsocks.Model; | |||
using Shadowsocks.Properties; | |||
using System; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Diagnostics; | |||
using System.IO; | |||
using System.IO.Compression; | |||
using System.Text; | |||
using System.Net.NetworkInformation; | |||
using System.Net; | |||
using System.Net.NetworkInformation; | |||
using System.Runtime.InteropServices; | |||
using System.Text; | |||
using Shadowsocks.Model; | |||
using Shadowsocks.Properties; | |||
using Shadowsocks.Util; | |||
namespace Shadowsocks.Controller | |||
@@ -16,16 +16,14 @@ namespace Shadowsocks.Controller | |||
class PolipoRunner | |||
{ | |||
private Process _process; | |||
private static string temppath; | |||
private int _runningPort; | |||
static PolipoRunner() | |||
{ | |||
temppath = Utils.GetTempPath(); | |||
try | |||
{ | |||
FileManager.UncompressFile(temppath + "/ss_privoxy.exe", Resources.privoxy_exe); | |||
FileManager.UncompressFile(temppath + "/mgwz.dll", Resources.mgwz_dll); | |||
FileManager.UncompressFile(Utils.GetTempPath("ss_privoxy.exe"), Resources.privoxy_exe); | |||
FileManager.UncompressFile(Utils.GetTempPath("mgwz.dll"), Resources.mgwz_dll); | |||
} | |||
catch (IOException e) | |||
{ | |||
@@ -56,7 +54,7 @@ namespace Shadowsocks.Controller | |||
} | |||
catch (Exception e) | |||
{ | |||
Console.WriteLine(e.ToString()); | |||
Logging.LogUsefulException(e); | |||
} | |||
} | |||
string polipoConfig = Resources.privoxy_conf; | |||
@@ -64,20 +62,16 @@ namespace Shadowsocks.Controller | |||
polipoConfig = polipoConfig.Replace("__SOCKS_PORT__", configuration.localPort.ToString()); | |||
polipoConfig = polipoConfig.Replace("__POLIPO_BIND_PORT__", _runningPort.ToString()); | |||
polipoConfig = polipoConfig.Replace("__POLIPO_BIND_IP__", configuration.shareOverLan ? "0.0.0.0" : "127.0.0.1"); | |||
FileManager.ByteArrayToFile(temppath + "/privoxy.conf", System.Text.Encoding.UTF8.GetBytes(polipoConfig)); | |||
FileManager.ByteArrayToFile(Utils.GetTempPath("privoxy.conf"), Encoding.UTF8.GetBytes(polipoConfig)); | |||
if (!(temppath.EndsWith("\\") || temppath.EndsWith("/"))) { | |||
temppath = temppath + "\\"; | |||
} | |||
_process = new Process(); | |||
// Configure the process using the StartInfo properties. | |||
_process.StartInfo.FileName = temppath + "ss_privoxy.exe"; | |||
_process.StartInfo.Arguments = " \"" + temppath + "privoxy.conf\""; | |||
_process.StartInfo.FileName = "ss_privoxy.exe"; | |||
_process.StartInfo.Arguments = "privoxy.conf"; | |||
_process.StartInfo.WorkingDirectory = Utils.GetTempPath(); | |||
_process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; | |||
_process.StartInfo.UseShellExecute = true; | |||
_process.StartInfo.CreateNoWindow = true; | |||
//_process.StartInfo.RedirectStandardOutput = true; | |||
//_process.StartInfo.RedirectStandardError = true; | |||
_process.Start(); | |||
} | |||
RefreshTrayArea(); | |||
@@ -94,7 +88,7 @@ namespace Shadowsocks.Controller | |||
} | |||
catch (Exception e) | |||
{ | |||
Console.WriteLine(e.ToString()); | |||
Logging.LogUsefulException(e); | |||
} | |||
_process = null; | |||
} | |||
@@ -1,8 +1,6 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Net; | |||
using System.Net.Sockets; | |||
using System.Text; | |||
namespace Shadowsocks.Controller | |||
{ | |||
@@ -140,7 +138,6 @@ namespace Shadowsocks.Controller | |||
} | |||
else | |||
{ | |||
//Console.WriteLine("bytesRead: " + bytesRead.ToString()); | |||
_local.Shutdown(SocketShutdown.Send); | |||
_localShutdown = true; | |||
CheckClose(); | |||
@@ -1,31 +1,30 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Text; | |||
using System.Net.Sockets; | |||
using System.Net; | |||
using System.Net.Sockets; | |||
using System.Timers; | |||
using Shadowsocks.Controller.Strategy; | |||
using Shadowsocks.Encryption; | |||
using Shadowsocks.Model; | |||
using Shadowsocks.Controller.Strategy; | |||
using System.Timers; | |||
namespace Shadowsocks.Controller | |||
{ | |||
class TCPRelay : Listener.Service | |||
{ | |||
private ShadowsocksController _controller; | |||
private DateTime _lastSweepTime; | |||
public ISet<Handler> Handlers | |||
public ISet<TCPHandler> Handlers | |||
{ | |||
get; set; | |||
} | |||
public TCPRelay(ShadowsocksController controller) | |||
{ | |||
this._controller = controller; | |||
this.Handlers = new HashSet<Handler>(); | |||
this._lastSweepTime = DateTime.Now; | |||
_controller = controller; | |||
Handlers = new HashSet<TCPHandler>(); | |||
_lastSweepTime = DateTime.Now; | |||
} | |||
public bool Handle(byte[] firstPacket, int length, Socket socket, object state) | |||
@@ -39,22 +38,21 @@ namespace Shadowsocks.Controller | |||
return false; | |||
} | |||
socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); | |||
Handler handler = new Handler(); | |||
TCPHandler handler = new TCPHandler(this); | |||
handler.connection = socket; | |||
handler.controller = _controller; | |||
handler.relay = this; | |||
handler.Start(firstPacket, length); | |||
IList<Handler> handlersToClose = new List<Handler>(); | |||
lock (this.Handlers) | |||
IList<TCPHandler> handlersToClose = new List<TCPHandler>(); | |||
lock (Handlers) | |||
{ | |||
this.Handlers.Add(handler); | |||
Logging.Debug($"connections: {Handlers.Count}"); | |||
Handlers.Add(handler); | |||
DateTime now = DateTime.Now; | |||
if (now - _lastSweepTime > TimeSpan.FromSeconds(1)) | |||
{ | |||
_lastSweepTime = now; | |||
foreach (Handler handler1 in this.Handlers) | |||
foreach (TCPHandler handler1 in Handlers) | |||
{ | |||
if (now - handler1.lastActivity > TimeSpan.FromSeconds(900)) | |||
{ | |||
@@ -63,18 +61,28 @@ namespace Shadowsocks.Controller | |||
} | |||
} | |||
} | |||
foreach (Handler handler1 in handlersToClose) | |||
foreach (TCPHandler handler1 in handlersToClose) | |||
{ | |||
Logging.Debug("Closing timed out connection"); | |||
Logging.Debug("Closing timed out TCP connection."); | |||
handler1.Close(); | |||
} | |||
return true; | |||
return true; | |||
} | |||
public void UpdateInboundCounter(long n) | |||
{ | |||
_controller.UpdateInboundCounter(n); | |||
} | |||
public void UpdateOutboundCounter(long n) | |||
{ | |||
_controller.UpdateOutboundCounter(n); | |||
} | |||
} | |||
class Handler | |||
class TCPHandler | |||
{ | |||
//public Encryptor encryptor; | |||
// public Encryptor encryptor; | |||
public IEncryptor encryptor; | |||
public Server server; | |||
// Client socket. | |||
@@ -85,6 +93,7 @@ namespace Shadowsocks.Controller | |||
public DateTime lastActivity; | |||
private const int maxRetry = 4; | |||
private int retryCount = 0; | |||
private bool connected; | |||
@@ -93,17 +102,18 @@ namespace Shadowsocks.Controller | |||
private int _firstPacketLength; | |||
// Size of receive buffer. | |||
public const int RecvSize = 8192; | |||
public const int BufferSize = RecvSize + 32; | |||
public const int RecvReserveSize = IVEncryptor.ONETIMEAUTH_BYTES + IVEncryptor.AUTH_BYTES; // reserve for one-time auth | |||
public const int BufferSize = RecvSize + RecvReserveSize + 32; | |||
private int totalRead = 0; | |||
private int totalWrite = 0; | |||
// remote receive buffer | |||
private byte[] remoteRecvBuffer = new byte[RecvSize]; | |||
private byte[] remoteRecvBuffer = new byte[BufferSize]; | |||
// remote send buffer | |||
private byte[] remoteSendBuffer = new byte[BufferSize]; | |||
// connection receive buffer | |||
private byte[] connetionRecvBuffer = new byte[RecvSize]; | |||
private byte[] connetionRecvBuffer = new byte[BufferSize]; | |||
// connection send buffer | |||
private byte[] connetionSendBuffer = new byte[BufferSize]; | |||
// Received data string. | |||
@@ -116,6 +126,12 @@ namespace Shadowsocks.Controller | |||
private object decryptionLock = new object(); | |||
private DateTime _startConnectTime; | |||
private TCPRelay tcprelay; // TODO: tcprelay ?= relay | |||
public TCPHandler(TCPRelay tcprelay) | |||
{ | |||
this.tcprelay = tcprelay; | |||
} | |||
public void CreateRemote() | |||
{ | |||
@@ -124,23 +140,23 @@ namespace Shadowsocks.Controller | |||
{ | |||
throw new ArgumentException("No server configured"); | |||
} | |||
this.encryptor = EncryptorFactory.GetEncryptor(server.method, server.password); | |||
encryptor = EncryptorFactory.GetEncryptor(server.method, server.password, server.auth, false); | |||
this.server = server; | |||
} | |||
public void Start(byte[] firstPacket, int length) | |||
{ | |||
this._firstPacket = firstPacket; | |||
this._firstPacketLength = length; | |||
this.HandshakeReceive(); | |||
this.lastActivity = DateTime.Now; | |||
_firstPacket = firstPacket; | |||
_firstPacketLength = length; | |||
HandshakeReceive(); | |||
lastActivity = DateTime.Now; | |||
} | |||
private void CheckClose() | |||
{ | |||
if (connectionShutdown && remoteShutdown) | |||
{ | |||
this.Close(); | |||
Close(); | |||
} | |||
} | |||
@@ -148,7 +164,6 @@ namespace Shadowsocks.Controller | |||
{ | |||
lock (relay.Handlers) | |||
{ | |||
Logging.Debug($"connections: {relay.Handlers.Count}"); | |||
relay.Handlers.Remove(this); | |||
} | |||
lock (this) | |||
@@ -212,19 +227,19 @@ namespace Shadowsocks.Controller | |||
{ | |||
// reject socks 4 | |||
response = new byte[] { 0, 91 }; | |||
Console.WriteLine("socks 5 protocol error"); | |||
Logging.Error("socks 5 protocol error"); | |||
} | |||
connection.BeginSend(response, 0, response.Length, 0, new AsyncCallback(HandshakeSendCallback), null); | |||
} | |||
else | |||
{ | |||
this.Close(); | |||
Close(); | |||
} | |||
} | |||
catch (Exception e) | |||
{ | |||
Logging.LogUsefulException(e); | |||
this.Close(); | |||
Close(); | |||
} | |||
} | |||
@@ -238,20 +253,19 @@ namespace Shadowsocks.Controller | |||
{ | |||
connection.EndSend(ar); | |||
// +----+-----+-------+------+----------+----------+ | |||
// |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | | |||
// +----+-----+-------+------+----------+----------+ | |||
// | 1 | 1 | X'00' | 1 | Variable | 2 | | |||
// +----+-----+-------+------+----------+----------+ | |||
// +-----+-----+-------+------+----------+----------+ | |||
// | VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | | |||
// +-----+-----+-------+------+----------+----------+ | |||
// | 1 | 1 | X'00' | 1 | Variable | 2 | | |||
// +-----+-----+-------+------+----------+----------+ | |||
// Skip first 3 bytes | |||
// TODO validate | |||
connection.BeginReceive(connetionRecvBuffer, 0, 3, 0, | |||
new AsyncCallback(handshakeReceive2Callback), null); | |||
connection.BeginReceive(connetionRecvBuffer, 0, 3, 0, new AsyncCallback(handshakeReceive2Callback), null); | |||
} | |||
catch (Exception e) | |||
{ | |||
Logging.LogUsefulException(e); | |||
this.Close(); | |||
Close(); | |||
} | |||
} | |||
@@ -280,14 +294,14 @@ namespace Shadowsocks.Controller | |||
} | |||
else | |||
{ | |||
Console.WriteLine("failed to recv data in handshakeReceive2Callback"); | |||
this.Close(); | |||
Logging.Debug("failed to recv data in Shadowsocks.Controller.TCPHandler.handshakeReceive2Callback()"); | |||
Close(); | |||
} | |||
} | |||
catch (Exception e) | |||
{ | |||
Logging.LogUsefulException(e); | |||
this.Close(); | |||
Close(); | |||
} | |||
} | |||
@@ -323,27 +337,27 @@ namespace Shadowsocks.Controller | |||
if (ar.AsyncState != null) | |||
{ | |||
connection.EndSend(ar); | |||
connection.BeginReceive(connetionRecvBuffer, 0, RecvSize, 0, | |||
new AsyncCallback(ReadAll), null); | |||
Logging.Debug(remote, RecvSize, "TCP Relay"); | |||
connection.BeginReceive(connetionRecvBuffer, 0, RecvSize, 0, new AsyncCallback(ReadAll), null); | |||
} | |||
else | |||
{ | |||
int bytesRead = connection.EndReceive(ar); | |||
if (bytesRead > 0) | |||
{ | |||
connection.BeginReceive(connetionRecvBuffer, 0, RecvSize, 0, | |||
new AsyncCallback(ReadAll), null); | |||
Logging.Debug(remote, RecvSize, "TCP Relay"); | |||
connection.BeginReceive(connetionRecvBuffer, 0, RecvSize, 0, new AsyncCallback(ReadAll), null); | |||
} | |||
else | |||
{ | |||
this.Close(); | |||
Close(); | |||
} | |||
} | |||
} | |||
catch (Exception e) | |||
{ | |||
Logging.LogUsefulException(e); | |||
this.Close(); | |||
Close(); | |||
} | |||
} | |||
@@ -359,15 +373,16 @@ namespace Shadowsocks.Controller | |||
catch (Exception e) | |||
{ | |||
Logging.LogUsefulException(e); | |||
this.Close(); | |||
Close(); | |||
} | |||
} | |||
// inner class | |||
private class ServerTimer : Timer | |||
{ | |||
public Server Server; | |||
public ServerTimer(int p) :base(p) | |||
public ServerTimer(int p) : base(p) | |||
{ | |||
} | |||
} | |||
@@ -401,13 +416,12 @@ namespace Shadowsocks.Controller | |||
connected = false; | |||
// Connect to the remote endpoint. | |||
remote.BeginConnect(remoteEP, | |||
new AsyncCallback(ConnectCallback), connectTimer); | |||
remote.BeginConnect(remoteEP, new AsyncCallback(ConnectCallback), connectTimer); | |||
} | |||
catch (Exception e) | |||
{ | |||
Logging.LogUsefulException(e); | |||
this.Close(); | |||
Close(); | |||
} | |||
} | |||
@@ -423,22 +437,22 @@ namespace Shadowsocks.Controller | |||
{ | |||
strategy.SetFailure(server); | |||
} | |||
Console.WriteLine(String.Format("{0} timed out", server.FriendlyName())); | |||
Logging.Info($"{server.FriendlyName()} timed out"); | |||
remote.Close(); | |||
RetryConnect(); | |||
} | |||
private void RetryConnect() | |||
{ | |||
if (retryCount < 4) | |||
if (retryCount < maxRetry) | |||
{ | |||
Logging.Debug("Connection failed, retrying"); | |||
Logging.Debug($"Connection failed, retry ({retryCount})"); | |||
StartConnect(); | |||
retryCount++; | |||
} | |||
else | |||
{ | |||
this.Close(); | |||
Close(); | |||
} | |||
} | |||
@@ -462,8 +476,7 @@ namespace Shadowsocks.Controller | |||
connected = true; | |||
//Console.WriteLine("Socket connected to {0}", | |||
// remote.RemoteEndPoint.ToString()); | |||
Logging.Debug($"Socket connected to {remote.RemoteEndPoint}"); | |||
var latency = DateTime.Now - _startConnectTime; | |||
IStrategy strategy = controller.GetCurrentStrategy(); | |||
@@ -500,15 +513,13 @@ namespace Shadowsocks.Controller | |||
} | |||
try | |||
{ | |||
remote.BeginReceive(remoteRecvBuffer, 0, RecvSize, 0, | |||
new AsyncCallback(PipeRemoteReceiveCallback), null); | |||
connection.BeginReceive(connetionRecvBuffer, 0, RecvSize, 0, | |||
new AsyncCallback(PipeConnectionReceiveCallback), null); | |||
remote.BeginReceive(remoteRecvBuffer, 0, RecvSize, 0, new AsyncCallback(PipeRemoteReceiveCallback), null); | |||
connection.BeginReceive(connetionRecvBuffer, 0, RecvSize, 0, new AsyncCallback(PipeConnectionReceiveCallback), null); | |||
} | |||
catch (Exception e) | |||
{ | |||
Logging.LogUsefulException(e); | |||
this.Close(); | |||
Close(); | |||
} | |||
} | |||
@@ -522,10 +533,11 @@ namespace Shadowsocks.Controller | |||
{ | |||
int bytesRead = remote.EndReceive(ar); | |||
totalRead += bytesRead; | |||
tcprelay.UpdateInboundCounter(bytesRead); | |||
if (bytesRead > 0) | |||
{ | |||
this.lastActivity = DateTime.Now; | |||
lastActivity = DateTime.Now; | |||
int bytesToSend; | |||
lock (decryptionLock) | |||
{ | |||
@@ -535,33 +547,33 @@ namespace Shadowsocks.Controller | |||
} | |||
encryptor.Decrypt(remoteRecvBuffer, bytesRead, remoteSendBuffer, out bytesToSend); | |||
} | |||
Logging.Debug(remote, bytesToSend, "TCP Relay", "@PipeRemoteReceiveCallback() (download)"); | |||
connection.BeginSend(remoteSendBuffer, 0, bytesToSend, 0, new AsyncCallback(PipeConnectionSendCallback), null); | |||
IStrategy strategy = controller.GetCurrentStrategy(); | |||
if (strategy != null) | |||
{ | |||
strategy.UpdateLastRead(this.server); | |||
strategy.UpdateLastRead(server); | |||
} | |||
} | |||
else | |||
{ | |||
//Console.WriteLine("bytesRead: " + bytesRead.ToString()); | |||
connection.Shutdown(SocketShutdown.Send); | |||
connectionShutdown = true; | |||
CheckClose(); | |||
if (totalRead == 0) | |||
{ | |||
// closed before anything received, reports as failure | |||
// disable this feature | |||
// controller.GetCurrentStrategy().SetFailure(this.server); | |||
} | |||
//if (totalRead == 0) | |||
//{ | |||
// // closed before anything received, reports as failure | |||
// // disable this feature | |||
// controller.GetCurrentStrategy().SetFailure(this.server); | |||
//} | |||
} | |||
} | |||
catch (Exception e) | |||
{ | |||
Logging.LogUsefulException(e); | |||
this.Close(); | |||
Close(); | |||
} | |||
} | |||
@@ -587,12 +599,14 @@ namespace Shadowsocks.Controller | |||
} | |||
encryptor.Encrypt(connetionRecvBuffer, bytesRead, connetionSendBuffer, out bytesToSend); | |||
} | |||
Logging.Debug(remote, bytesToSend, "TCP Relay", "@PipeConnectionReceiveCallback() (upload)"); | |||
tcprelay.UpdateOutboundCounter(bytesToSend); | |||
remote.BeginSend(connetionSendBuffer, 0, bytesToSend, 0, new AsyncCallback(PipeRemoteSendCallback), null); | |||
IStrategy strategy = controller.GetCurrentStrategy(); | |||
if (strategy != null) | |||
{ | |||
strategy.UpdateLastWrite(this.server); | |||
strategy.UpdateLastWrite(server); | |||
} | |||
} | |||
else | |||
@@ -605,7 +619,7 @@ namespace Shadowsocks.Controller | |||
catch (Exception e) | |||
{ | |||
Logging.LogUsefulException(e); | |||
this.Close(); | |||
Close(); | |||
} | |||
} | |||
@@ -618,13 +632,12 @@ namespace Shadowsocks.Controller | |||
try | |||
{ | |||
remote.EndSend(ar); | |||
connection.BeginReceive(this.connetionRecvBuffer, 0, RecvSize, 0, | |||
new AsyncCallback(PipeConnectionReceiveCallback), null); | |||
connection.BeginReceive(connetionRecvBuffer, 0, RecvSize, 0, new AsyncCallback(PipeConnectionReceiveCallback), null); | |||
} | |||
catch (Exception e) | |||
{ | |||
Logging.LogUsefulException(e); | |||
this.Close(); | |||
Close(); | |||
} | |||
} | |||
@@ -637,13 +650,12 @@ namespace Shadowsocks.Controller | |||
try | |||
{ | |||
connection.EndSend(ar); | |||
remote.BeginReceive(this.remoteRecvBuffer, 0, RecvSize, 0, | |||
new AsyncCallback(PipeRemoteReceiveCallback), null); | |||
remote.BeginReceive(remoteRecvBuffer, 0, RecvSize, 0, new AsyncCallback(PipeRemoteReceiveCallback), null); | |||
} | |||
catch (Exception e) | |||
{ | |||
Logging.LogUsefulException(e); | |||
this.Close(); | |||
Close(); | |||
} | |||
} | |||
} | |||
@@ -1,12 +1,12 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Text; | |||
using Shadowsocks.Encryption; | |||
using Shadowsocks.Model; | |||
using System.Net.Sockets; | |||
using System.Net; | |||
using System.Net.Sockets; | |||
using System.Runtime.CompilerServices; | |||
using Shadowsocks.Controller.Strategy; | |||
using Shadowsocks.Encryption; | |||
using Shadowsocks.Model; | |||
namespace Shadowsocks.Controller | |||
{ | |||
@@ -14,6 +14,10 @@ namespace Shadowsocks.Controller | |||
{ | |||
private ShadowsocksController _controller; | |||
private LRUCache<IPEndPoint, UDPHandler> _cache; | |||
public long outbound = 0; | |||
public long inbound = 0; | |||
public UDPRelay(ShadowsocksController controller) | |||
{ | |||
this._controller = controller; | |||
@@ -70,23 +74,27 @@ namespace Shadowsocks.Controller | |||
} | |||
_remoteEndPoint = new IPEndPoint(ipAddress, server.server_port); | |||
_remote = new Socket(_remoteEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp); | |||
} | |||
public void Send(byte[] data, int length) | |||
{ | |||
IEncryptor encryptor = EncryptorFactory.GetEncryptor(_server.method, _server.password); | |||
byte[] dataIn = new byte[length - 3]; | |||
IEncryptor encryptor = EncryptorFactory.GetEncryptor(_server.method, _server.password, _server.auth, true); | |||
byte[] dataIn = new byte[length - 3 + IVEncryptor.ONETIMEAUTH_BYTES]; | |||
Array.Copy(data, 3, dataIn, 0, length - 3); | |||
byte[] dataOut = new byte[length - 3 + 16]; | |||
byte[] dataOut = new byte[length - 3 + 16 + IVEncryptor.ONETIMEAUTH_BYTES]; | |||
int outlen; | |||
encryptor.Encrypt(dataIn, dataIn.Length, dataOut, out outlen); | |||
_remote.SendTo(dataOut, _remoteEndPoint); | |||
encryptor.Encrypt(dataIn, length - 3, dataOut, out outlen); | |||
Logging.Debug(_localEndPoint, _remoteEndPoint, outlen, "UDP Relay"); | |||
_remote.SendTo(dataOut, outlen, SocketFlags.None, _remoteEndPoint); | |||
} | |||
public void Receive() | |||
{ | |||
EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0); | |||
Logging.Debug($"++++++Receive Server Port, size:" + _buffer.Length); | |||
_remote.BeginReceiveFrom(_buffer, 0, _buffer.Length, 0, ref remoteEndPoint, new AsyncCallback(RecvFromCallback), null); | |||
} | |||
public void RecvFromCallback(IAsyncResult ar) | |||
{ | |||
try | |||
@@ -97,12 +105,13 @@ namespace Shadowsocks.Controller | |||
byte[] dataOut = new byte[bytesRead]; | |||
int outlen; | |||
IEncryptor encryptor = EncryptorFactory.GetEncryptor(_server.method, _server.password); | |||
IEncryptor encryptor = EncryptorFactory.GetEncryptor(_server.method, _server.password, _server.auth, true); | |||
encryptor.Decrypt(_buffer, bytesRead, dataOut, out outlen); | |||
byte[] sendBuf = new byte[outlen + 3]; | |||
Array.Copy(dataOut, 0, sendBuf, 3, outlen); | |||
Logging.Debug(_localEndPoint, _remoteEndPoint, outlen, "UDP Relay"); | |||
_local.SendTo(sendBuf, outlen + 3, 0, _localEndPoint); | |||
Receive(); | |||
} | |||
@@ -118,6 +127,7 @@ namespace Shadowsocks.Controller | |||
{ | |||
} | |||
} | |||
public void Close() | |||
{ | |||
try | |||
@@ -139,7 +149,6 @@ namespace Shadowsocks.Controller | |||
} | |||
} | |||
// cc by-sa 3.0 http://stackoverflow.com/a/3719378/1124054 | |||
class LRUCache<K, V> where V : UDPRelay.UDPHandler | |||
{ | |||
@@ -1,92 +1,73 @@ | |||
using Shadowsocks.Model; | |||
using System; | |||
using System.Collections; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Net; | |||
using System.Reflection; | |||
using System.Text; | |||
using System.Text.RegularExpressions; | |||
using SimpleJson; | |||
using Newtonsoft.Json.Linq; | |||
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.6"; | |||
public const string Version = "2.5.8.2"; | |||
public void CheckUpdate(Configuration config) | |||
private class CheckUpdateTimer : System.Timers.Timer | |||
{ | |||
// 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)); | |||
} | |||
public Configuration config; | |||
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++) | |||
public CheckUpdateTimer(int p) : base(p) | |||
{ | |||
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; | |||
} | |||
public class VersionComparer : IComparer<string> | |||
public void CheckUpdate(Configuration config, int delay) | |||
{ | |||
// Calls CaseInsensitiveComparer.Compare with the parameters reversed. | |||
public int Compare(string x, string y) | |||
{ | |||
return CompareVersion(ParseVersionFromURL(x), ParseVersionFromURL(y)); | |||
} | |||
CheckUpdateTimer timer = new CheckUpdateTimer(delay); | |||
timer.AutoReset = false; | |||
timer.Elapsed += Timer_Elapsed; | |||
timer.config = config; | |||
timer.Enabled = true; | |||
} | |||
private static string ParseVersionFromURL(string url) | |||
private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) | |||
{ | |||
Match match = Regex.Match(url, @".*Shadowsocks-win.*?-([\d\.]+)\.\w+", RegexOptions.IgnoreCase); | |||
if (match.Success) | |||
{ | |||
if (match.Groups.Count == 2) | |||
{ | |||
return match.Groups[1].Value; | |||
} | |||
} | |||
return null; | |||
CheckUpdateTimer timer = (CheckUpdateTimer)sender; | |||
Configuration config = timer.config; | |||
timer.Elapsed -= Timer_Elapsed; | |||
timer.Enabled = false; | |||
timer.Dispose(); | |||
CheckUpdate(config); | |||
} | |||
private void SortVersions(List<string> versions) | |||
public void CheckUpdate(Configuration config) | |||
{ | |||
versions.Sort(new VersionComparer()); | |||
} | |||
this.config = config; | |||
private bool IsNewVersion(string url) | |||
{ | |||
if (url.IndexOf("prerelease") >= 0) | |||
try | |||
{ | |||
return false; | |||
Logging.Debug("Checking updates..."); | |||
WebClient http = CreateWebClient(); | |||
http.DownloadStringCompleted += http_DownloadStringCompleted; | |||
http.DownloadStringAsync(new Uri(UpdateURL)); | |||
} | |||
string version = ParseVersionFromURL(url); | |||
if (version == null) | |||
catch (Exception ex) | |||
{ | |||
return false; | |||
Logging.LogUsefulException(ex); | |||
} | |||
string currentVersion = Version; | |||
return CompareVersion(version, currentVersion) > 0; | |||
} | |||
private void http_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e) | |||
@@ -95,42 +76,167 @@ namespace Shadowsocks.Controller | |||
{ | |||
string response = e.Result; | |||
JsonArray result = (JsonArray)SimpleJson.SimpleJson.DeserializeObject(e.Result); | |||
JArray result = JArray.Parse(response); | |||
List<string> versions = new List<string>(); | |||
foreach (JsonObject release in result) | |||
List<Asset> asserts = new List<Asset>(); | |||
if (result != null) | |||
{ | |||
if ((bool)release["prerelease"]) | |||
{ | |||
continue; | |||
} | |||
foreach (JsonObject asset in (JsonArray)release["assets"]) | |||
foreach (JObject release in result) | |||
{ | |||
string url = (string)asset["browser_download_url"]; | |||
if (IsNewVersion(url)) | |||
if ((bool)release["prerelease"]) | |||
{ | |||
continue; | |||
} | |||
foreach (JObject asset in (JArray)release["assets"]) | |||
{ | |||
versions.Add(url); | |||
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; | |||
if (versions.Count == 0) | |||
startDownload(); | |||
} | |||
else | |||
{ | |||
Logging.Debug("No update is available"); | |||
if (CheckUpdateCompleted != null) | |||
{ | |||
CheckUpdateCompleted(this, new EventArgs()); | |||
} | |||
} | |||
} | |||
catch (Exception ex) | |||
{ | |||
Logging.LogUsefulException(ex); | |||
} | |||
} | |||
private void startDownload() | |||
{ | |||
try | |||
{ | |||
LatestVersionLocalName = Utils.GetTempPath(LatestVersionName); | |||
WebClient http = CreateWebClient(); | |||
http.DownloadFileCompleted += Http_DownloadFileCompleted; | |||
http.DownloadFileAsync(new Uri(LatestVersionURL), LatestVersionLocalName); | |||
} | |||
catch (Exception ex) | |||
{ | |||
Logging.LogUsefulException(ex); | |||
} | |||
} | |||
private void Http_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e) | |||
{ | |||
try | |||
{ | |||
if (e.Error != null) | |||
{ | |||
Logging.LogUsefulException(e.Error); | |||
return; | |||
} | |||
// sort versions | |||
SortVersions(versions); | |||
LatestVersionURL = versions[versions.Count - 1]; | |||
LatestVersionNumber = ParseVersionFromURL(LatestVersionURL); | |||
if (NewVersionFound != null) | |||
Logging.Debug($"New version {LatestVersionNumber} found: {LatestVersionLocalName}"); | |||
if (CheckUpdateCompleted != null) | |||
{ | |||
NewVersionFound(this, new EventArgs()); | |||
CheckUpdateCompleted(this, new EventArgs()); | |||
} | |||
} | |||
catch (Exception ex) | |||
{ | |||
Logging.Debug(ex.ToString()); | |||
return; | |||
Logging.LogUsefulException(ex); | |||
} | |||
} | |||
private WebClient CreateWebClient() | |||
{ | |||
WebClient http = new WebClient(); | |||
http.Headers.Add("User-Agent", UserAgent); | |||
http.Proxy = new WebProxy(IPAddress.Loopback.ToString(), config.localPort); | |||
return http; | |||
} | |||
private void SortByVersions(List<Asset> asserts) | |||
{ | |||
asserts.Sort(new VersionComparer()); | |||
} | |||
public class Asset | |||
{ | |||
public bool prerelease; | |||
public string name; | |||
public string version; | |||
public string browser_download_url; | |||
public bool IsNewVersion(string currentVersion) | |||
{ | |||
if (prerelease) | |||
{ | |||
return false; | |||
} | |||
if (version == null) | |||
{ | |||
return false; | |||
} | |||
return CompareVersion(version, currentVersion) > 0; | |||
} | |||
public void Parse(JObject 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; | |||
} | |||
private static string ParseVersionFromURL(string url) | |||
{ | |||
Match match = Regex.Match(url, @".*Shadowsocks-win.*?-([\d\.]+)\.\w+", RegexOptions.IgnoreCase); | |||
if (match.Success) | |||
{ | |||
if (match.Groups.Count == 2) | |||
{ | |||
return match.Groups[1].Value; | |||
} | |||
} | |||
return 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++) | |||
{ | |||
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; | |||
} | |||
} | |||
class VersionComparer : IComparer<Asset> | |||
{ | |||
// Calls CaseInsensitiveComparer.Compare with the parameters reversed. | |||
public int Compare(Asset x, Asset y) | |||
{ | |||
return Asset.CompareVersion(x.version, y.version); | |||
} | |||
} | |||
} | |||
@@ -1,12 +1,17 @@ | |||
using System.IO; | |||
using Shadowsocks.Model; | |||
using System; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.IO; | |||
using System.Net; | |||
using System.Net.Sockets; | |||
using System.Text; | |||
using System.Threading; | |||
using System.Net.Sockets; | |||
using Newtonsoft.Json; | |||
using Shadowsocks.Controller.Strategy; | |||
using System.Net; | |||
using Shadowsocks.Model; | |||
using Shadowsocks.Properties; | |||
using Shadowsocks.Util; | |||
namespace Shadowsocks.Controller | |||
{ | |||
@@ -25,7 +30,12 @@ namespace Shadowsocks.Controller | |||
private StrategyManager _strategyManager; | |||
private PolipoRunner polipoRunner; | |||
private GFWListUpdater gfwListUpdater; | |||
private AvailabilityStatistics _availabilityStatics; | |||
public AvailabilityStatistics availabilityStatistics { get; private set; } | |||
public StatisticsStrategyConfiguration StatisticsConfiguration { get; private set; } | |||
public long inboundCounter = 0; | |||
public long outboundCounter = 0; | |||
private bool stopped = false; | |||
private bool _systemProxyIsDirty = false; | |||
@@ -53,6 +63,7 @@ namespace Shadowsocks.Controller | |||
public ShadowsocksController() | |||
{ | |||
_config = Configuration.Load(); | |||
StatisticsConfiguration = StatisticsStrategyConfiguration.Load(); | |||
_strategyManager = new StrategyManager(this); | |||
StartReleasingMemory(); | |||
} | |||
@@ -122,7 +133,13 @@ namespace Shadowsocks.Controller | |||
{ | |||
_config.configs = servers; | |||
_config.localPort = localPort; | |||
SaveConfig(_config); | |||
Configuration.Save(_config); | |||
} | |||
public void SaveStrategyConfigurations(StatisticsStrategyConfiguration configuration) | |||
{ | |||
StatisticsConfiguration = configuration; | |||
StatisticsStrategyConfiguration.Save(configuration); | |||
} | |||
public bool AddServerBySSURL(string ssURL) | |||
@@ -236,7 +253,7 @@ namespace Shadowsocks.Controller | |||
public static string GetQRCode(Server server) | |||
{ | |||
string parts = server.method + ":" + server.password + "@" + server.server + ":" + server.server_port; | |||
string base64 = System.Convert.ToBase64String(Encoding.UTF8.GetBytes(parts)); | |||
string base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(parts)); | |||
return "ss://" + base64; | |||
} | |||
@@ -248,14 +265,12 @@ namespace Shadowsocks.Controller | |||
} | |||
} | |||
public void ToggleAvailabilityStatistics(bool enabled) | |||
public void UpdateStatisticsConfiguration(bool enabled) | |||
{ | |||
if (_availabilityStatics != null) | |||
{ | |||
_availabilityStatics.Set(enabled); | |||
_config.availabilityStatistics = enabled; | |||
SaveConfig(_config); | |||
} | |||
if (availabilityStatistics == null) return; | |||
availabilityStatistics.UpdateConfiguration(_config, StatisticsConfiguration); | |||
_config.availabilityStatistics = enabled; | |||
SaveConfig(_config); | |||
} | |||
public void SavePACUrl(string pacUrl) | |||
@@ -280,10 +295,33 @@ namespace Shadowsocks.Controller | |||
} | |||
} | |||
public void ToggleCheckingUpdate(bool enabled) | |||
{ | |||
_config.autoCheckUpdate = enabled; | |||
Configuration.Save(_config); | |||
} | |||
public void SaveLogViewerConfig(LogViewerConfig newConfig) | |||
{ | |||
_config.logViewer = newConfig; | |||
Configuration.Save(_config); | |||
} | |||
public void UpdateInboundCounter(long n) | |||
{ | |||
Interlocked.Add(ref inboundCounter, n); | |||
} | |||
public void UpdateOutboundCounter(long n) | |||
{ | |||
Interlocked.Add(ref outboundCounter, n); | |||
} | |||
protected void Reload() | |||
{ | |||
// some logic in configuration updated the config when saving, we need to read it again | |||
_config = Configuration.Load(); | |||
StatisticsConfiguration = StatisticsStrategyConfiguration.Load(); | |||
if (polipoRunner == null) | |||
{ | |||
@@ -293,6 +331,7 @@ namespace Shadowsocks.Controller | |||
{ | |||
_pacServer = new PACServer(); | |||
_pacServer.PACFileChanged += pacServer_PACFileChanged; | |||
_pacServer.UserRuleFileChanged += pacServer_UserRuleFileChanged; | |||
} | |||
_pacServer.UpdateConfiguration(_config); | |||
if (gfwListUpdater == null) | |||
@@ -302,17 +341,16 @@ namespace Shadowsocks.Controller | |||
gfwListUpdater.Error += pacServer_PACUpdateError; | |||
} | |||
if (_listener != null) | |||
if (availabilityStatistics == null) | |||
{ | |||
_listener.Stop(); | |||
availabilityStatistics = new AvailabilityStatistics(_config, StatisticsConfiguration); | |||
} | |||
availabilityStatistics.UpdateConfiguration(_config, StatisticsConfiguration); | |||
if (_availabilityStatics == null) | |||
if (_listener != null) | |||
{ | |||
_availabilityStatics = new AvailabilityStatistics(); | |||
_availabilityStatics.UpdateConfiguration(_config); | |||
_listener.Stop(); | |||
} | |||
// don't put polipoRunner.Start() before pacServer.Stop() | |||
// or bind will fail when switching bind address from 0.0.0.0 to 127.0.0.1 | |||
// though UseShellExecute is set to true now | |||
@@ -360,7 +398,7 @@ namespace Shadowsocks.Controller | |||
} | |||
UpdateSystemProxy(); | |||
Util.Utils.ReleaseMemory(true); | |||
Utils.ReleaseMemory(true); | |||
} | |||
protected void SaveConfig(Configuration newConfig) | |||
@@ -404,6 +442,47 @@ namespace Shadowsocks.Controller | |||
UpdatePACFromGFWListError(this, e); | |||
} | |||
private void pacServer_UserRuleFileChanged(object sender, EventArgs e) | |||
{ | |||
// TODO: this is a dirty hack. (from code GListUpdater.http_DownloadStringCompleted()) | |||
if (!File.Exists(Utils.GetTempPath("gfwlist.txt"))) | |||
{ | |||
UpdatePACFromGFWList(); | |||
return; | |||
} | |||
List<string> lines = GFWListUpdater.ParseResult(File.ReadAllText(Utils.GetTempPath("gfwlist.txt"))); | |||
if (File.Exists(PACServer.USER_RULE_FILE)) | |||
{ | |||
string local = File.ReadAllText(PACServer.USER_RULE_FILE, Encoding.UTF8); | |||
string[] rules = local.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); | |||
foreach (string rule in rules) | |||
{ | |||
if (rule.StartsWith("!") || rule.StartsWith("[")) | |||
continue; | |||
lines.Add(rule); | |||
} | |||
} | |||
string abpContent; | |||
if (File.Exists(PACServer.USER_ABP_FILE)) | |||
{ | |||
abpContent = File.ReadAllText(PACServer.USER_ABP_FILE, Encoding.UTF8); | |||
} | |||
else | |||
{ | |||
abpContent = Utils.UnGzip(Resources.abp_js); | |||
} | |||
abpContent = abpContent.Replace("__RULES__", JsonConvert.SerializeObject(lines, Formatting.Indented)); | |||
if (File.Exists(PACServer.PAC_FILE)) | |||
{ | |||
string original = File.ReadAllText(PACServer.PAC_FILE, Encoding.UTF8); | |||
if (original == abpContent) | |||
{ | |||
return; | |||
} | |||
} | |||
File.WriteAllText(PACServer.PAC_FILE, abpContent, Encoding.UTF8); | |||
} | |||
private void StartReleasingMemory() | |||
{ | |||
_ramThread = new Thread(new ThreadStart(ReleaseMemory)); | |||
@@ -415,7 +494,7 @@ namespace Shadowsocks.Controller | |||
{ | |||
while (true) | |||
{ | |||
Util.Utils.ReleaseMemory(false); | |||
Utils.ReleaseMemory(false); | |||
Thread.Sleep(30 * 1000); | |||
} | |||
} | |||
@@ -132,14 +132,14 @@ namespace Shadowsocks.Controller.Strategy | |||
if (_currentServer == null || max.score - _currentServer.score > 200) | |||
{ | |||
_currentServer = max; | |||
Console.WriteLine("HA switching to server: {0}", _currentServer.server.FriendlyName()); | |||
Logging.Info($"HA switching to server: {_currentServer.server.FriendlyName()}"); | |||
} | |||
} | |||
} | |||
public void UpdateLatency(Model.Server server, TimeSpan latency) | |||
{ | |||
Logging.Debug(String.Format("latency: {0} {1}", server.FriendlyName(), latency)); | |||
Logging.Debug($"latency: {server.FriendlyName()} {latency}"); | |||
ServerStatus status; | |||
if (_serverStatus.TryGetValue(server, out status)) | |||
@@ -151,7 +151,7 @@ namespace Shadowsocks.Controller.Strategy | |||
public void UpdateLastRead(Model.Server server) | |||
{ | |||
Logging.Debug(String.Format("last read: {0}", server.FriendlyName())); | |||
Logging.Debug($"last read: {server.FriendlyName()}"); | |||
ServerStatus status; | |||
if (_serverStatus.TryGetValue(server, out status)) | |||
@@ -162,7 +162,7 @@ namespace Shadowsocks.Controller.Strategy | |||
public void UpdateLastWrite(Model.Server server) | |||
{ | |||
Logging.Debug(String.Format("last write: {0}", server.FriendlyName())); | |||
Logging.Debug($"last write: {server.FriendlyName()}"); | |||
ServerStatus status; | |||
if (_serverStatus.TryGetValue(server, out status)) | |||
@@ -173,7 +173,7 @@ namespace Shadowsocks.Controller.Strategy | |||
public void SetFailure(Model.Server server) | |||
{ | |||
Logging.Debug(String.Format("failure: {0}", server.FriendlyName())); | |||
Logging.Debug($"failure: {server.FriendlyName()}"); | |||
ServerStatus status; | |||
if (_serverStatus.TryGetValue(server, out status)) | |||
@@ -1,176 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Net; | |||
using System.Text; | |||
using Shadowsocks.Model; | |||
using System.IO; | |||
using System.Net.NetworkInformation; | |||
using System.Threading; | |||
namespace Shadowsocks.Controller.Strategy | |||
{ | |||
class SimplyChooseByStatisticsStrategy : IStrategy | |||
{ | |||
private ShadowsocksController _controller; | |||
private Server _currentServer; | |||
private Timer timer; | |||
private Dictionary<string, StatisticsData> statistics; | |||
private static readonly int CachedInterval = 30 * 60 * 1000; //choose a new server every 30 minutes | |||
public SimplyChooseByStatisticsStrategy(ShadowsocksController controller) | |||
{ | |||
_controller = controller; | |||
var servers = controller.GetCurrentConfiguration().configs; | |||
int randomIndex = new Random().Next() % servers.Count(); | |||
_currentServer = servers[randomIndex]; //choose a server randomly at first | |||
timer = new Timer(ReloadStatisticsAndChooseAServer); | |||
} | |||
private void ReloadStatisticsAndChooseAServer(object obj) | |||
{ | |||
Logging.Debug("Reloading statistics and choose a new server...."); | |||
List<Server> servers = _controller.GetCurrentConfiguration().configs; | |||
LoadStatistics(); | |||
ChooseNewServer(servers); | |||
} | |||
/* | |||
return a dict: | |||
{ | |||
'ServerFriendlyName1':StatisticsData, | |||
'ServerFriendlyName2':... | |||
} | |||
*/ | |||
private void LoadStatistics() | |||
{ | |||
try | |||
{ | |||
var path = AvailabilityStatistics.AvailabilityStatisticsFile; | |||
Logging.Debug(string.Format("loading statistics from{0}", path)); | |||
statistics = (from l in File.ReadAllLines(path) | |||
.Skip(1) | |||
let strings = l.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries) | |||
let rawData = new | |||
{ | |||
ServerName = strings[1], | |||
IPStatus = strings[2], | |||
RoundtripTime = int.Parse(strings[3]) | |||
} | |||
group rawData by rawData.ServerName into server | |||
select new | |||
{ | |||
ServerName = server.Key, | |||
data = new StatisticsData | |||
{ | |||
SuccessTimes = server.Count(data => IPStatus.Success.ToString().Equals(data.IPStatus)), | |||
TimedOutTimes = server.Count(data => IPStatus.TimedOut.ToString().Equals(data.IPStatus)), | |||
AverageResponse = Convert.ToInt32(server.Average(data => data.RoundtripTime)), | |||
MinResponse = server.Min(data => data.RoundtripTime), | |||
MaxResponse = server.Max(data => data.RoundtripTime) | |||
} | |||
}).ToDictionary(server => server.ServerName, server => server.data); | |||
} | |||
catch (Exception e) | |||
{ | |||
Logging.LogUsefulException(e); | |||
} | |||
} | |||
//return the score by data | |||
//server with highest score will be choosen | |||
private static double GetScore(StatisticsData data) | |||
{ | |||
return (double)data.SuccessTimes / (data.SuccessTimes + data.TimedOutTimes); //simply choose min package loss | |||
} | |||
private class StatisticsData | |||
{ | |||
public int SuccessTimes; | |||
public int TimedOutTimes; | |||
public int AverageResponse; | |||
public int MinResponse; | |||
public int MaxResponse; | |||
} | |||
private void ChooseNewServer(List<Server> servers) | |||
{ | |||
if (statistics == null) | |||
{ | |||
return; | |||
} | |||
try | |||
{ | |||
var bestResult = (from server in servers | |||
let name = server.FriendlyName() | |||
where statistics.ContainsKey(name) | |||
select new | |||
{ | |||
server, | |||
score = GetScore(statistics[name]) | |||
} | |||
).Aggregate((result1, result2) => result1.score > result2.score ? result1 : result2); | |||
if (_controller.GetCurrentStrategy().ID == ID && _currentServer != bestResult.server) //output when enabled | |||
{ | |||
Console.WriteLine("Switch to server: {0} by package loss:{1}", bestResult.server.FriendlyName(), 1 - bestResult.score); | |||
} | |||
_currentServer = bestResult.server; | |||
} | |||
catch (Exception e) | |||
{ | |||
Logging.LogUsefulException(e); | |||
} | |||
} | |||
public string ID | |||
{ | |||
get { return "com.shadowsocks.strategy.scbs"; } | |||
} | |||
public string Name | |||
{ | |||
get { return I18N.GetString("Choose By Total Package Loss"); } | |||
} | |||
public Server GetAServer(IStrategyCallerType type, IPEndPoint localIPEndPoint) | |||
{ | |||
var oldServer = _currentServer; | |||
if (oldServer == null) | |||
{ | |||
ChooseNewServer(_controller.GetCurrentConfiguration().configs); | |||
} | |||
if (oldServer != _currentServer) | |||
{ | |||
} | |||
return _currentServer; //current server cached for CachedInterval | |||
} | |||
public void ReloadServers() | |||
{ | |||
ChooseNewServer(_controller.GetCurrentConfiguration().configs); | |||
timer?.Change(0, CachedInterval); | |||
} | |||
public void SetFailure(Server server) | |||
{ | |||
Logging.Debug(String.Format("failure: {0}", server.FriendlyName())); | |||
} | |||
public void UpdateLastRead(Server server) | |||
{ | |||
//TODO: combine this part of data with ICMP statics | |||
} | |||
public void UpdateLastWrite(Server server) | |||
{ | |||
//TODO: combine this part of data with ICMP statics | |||
} | |||
public void UpdateLatency(Server server, TimeSpan latency) | |||
{ | |||
//TODO: combine this part of data with ICMP statics | |||
} | |||
} | |||
} |
@@ -0,0 +1,153 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Net; | |||
using System.Net.NetworkInformation; | |||
using System.Threading; | |||
using Newtonsoft.Json; | |||
using Shadowsocks.Model; | |||
namespace Shadowsocks.Controller.Strategy | |||
{ | |||
class StatisticsStrategy : IStrategy | |||
{ | |||
private readonly ShadowsocksController _controller; | |||
private Server _currentServer; | |||
private readonly Timer _timer; | |||
private Dictionary<string, List<AvailabilityStatistics.RawStatisticsData>> _filteredStatistics; | |||
private int ChoiceKeptMilliseconds | |||
=> (int) TimeSpan.FromMinutes(_controller.StatisticsConfiguration.ChoiceKeptMinutes).TotalMilliseconds; | |||
public StatisticsStrategy(ShadowsocksController controller) | |||
{ | |||
_controller = controller; | |||
var servers = controller.GetCurrentConfiguration().configs; | |||
var randomIndex = new Random().Next() % servers.Count(); | |||
_currentServer = servers[randomIndex]; //choose a server randomly at first | |||
_timer = new Timer(ReloadStatisticsAndChooseAServer); | |||
} | |||
private void ReloadStatisticsAndChooseAServer(object obj) | |||
{ | |||
Logging.Debug("Reloading statistics and choose a new server...."); | |||
var servers = _controller.GetCurrentConfiguration().configs; | |||
LoadStatistics(); | |||
ChooseNewServer(servers); | |||
} | |||
private void LoadStatistics() | |||
{ | |||
_filteredStatistics = _controller.availabilityStatistics.RawStatistics ?? _filteredStatistics ?? new Dictionary<string, List<AvailabilityStatistics.RawStatisticsData>>(); | |||
} | |||
//return the score by data | |||
//server with highest score will be choosen | |||
private float GetScore(string serverName) | |||
{ | |||
var config = _controller.StatisticsConfiguration; | |||
List<AvailabilityStatistics.RawStatisticsData> dataList; | |||
if (_filteredStatistics == null || !_filteredStatistics.TryGetValue(serverName, out dataList)) return 0; | |||
var successTimes = (float) dataList.Count(data => data.ICMPStatus.Equals(IPStatus.Success.ToString())); | |||
var timedOutTimes = (float) dataList.Count(data => data.ICMPStatus.Equals(IPStatus.TimedOut.ToString())); | |||
var statisticsData = new AvailabilityStatistics.StatisticsData | |||
{ | |||
PackageLoss = timedOutTimes/(successTimes + timedOutTimes)*100, | |||
AverageResponse = Convert.ToInt32(dataList.Average(data => data.RoundtripTime)), | |||
MinResponse = dataList.Min(data => data.RoundtripTime), | |||
MaxResponse = dataList.Max(data => data.RoundtripTime) | |||
}; | |||
float factor; | |||
float score = 0; | |||
if (!config.Calculations.TryGetValue("PackageLoss", out factor)) factor = 0; | |||
score += statisticsData.PackageLoss*factor; | |||
if (!config.Calculations.TryGetValue("AverageResponse", out factor)) factor = 0; | |||
score += statisticsData.AverageResponse*factor; | |||
if (!config.Calculations.TryGetValue("MinResponse", out factor)) factor = 0; | |||
score += statisticsData.MinResponse*factor; | |||
if (!config.Calculations.TryGetValue("MaxResponse", out factor)) factor = 0; | |||
score += statisticsData.MaxResponse*factor; | |||
Logging.Debug($"{serverName} {JsonConvert.SerializeObject(statisticsData)}"); | |||
return score; | |||
} | |||
private void ChooseNewServer(List<Server> servers) | |||
{ | |||
if (_filteredStatistics == null || servers.Count == 0) | |||
{ | |||
return; | |||
} | |||
try | |||
{ | |||
var bestResult = (from server in servers | |||
let name = server.FriendlyName() | |||
where _filteredStatistics.ContainsKey(name) | |||
select new | |||
{ | |||
server, | |||
score = GetScore(name) | |||
} | |||
).Aggregate((result1, result2) => result1.score > result2.score ? result1 : result2); | |||
LogWhenEnabled($"Switch to server: {bestResult.server.FriendlyName()} by statistics: score {bestResult.score}"); | |||
_currentServer = bestResult.server; | |||
} | |||
catch (Exception e) | |||
{ | |||
Logging.LogUsefulException(e); | |||
} | |||
} | |||
private void LogWhenEnabled(string log) | |||
{ | |||
if (_controller.GetCurrentStrategy()?.ID == ID) //output when enabled | |||
{ | |||
Console.WriteLine(log); | |||
} | |||
} | |||
public string ID => "com.shadowsocks.strategy.scbs"; | |||
public string Name => I18N.GetString("Choose By Total Package Loss"); | |||
public Server GetAServer(IStrategyCallerType type, IPEndPoint localIPEndPoint) | |||
{ | |||
var oldServer = _currentServer; | |||
if (oldServer == null) | |||
{ | |||
ChooseNewServer(_controller.GetCurrentConfiguration().configs); | |||
} | |||
if (oldServer != _currentServer) | |||
{ | |||
} | |||
return _currentServer; //current server cached for CachedInterval | |||
} | |||
public void ReloadServers() | |||
{ | |||
ChooseNewServer(_controller.GetCurrentConfiguration().configs); | |||
_timer?.Change(0, ChoiceKeptMilliseconds); | |||
} | |||
public void SetFailure(Server server) | |||
{ | |||
Logging.Debug($"failure: {server.FriendlyName()}"); | |||
} | |||
public void UpdateLastRead(Server server) | |||
{ | |||
//TODO: combine this part of data with ICMP statics | |||
} | |||
public void UpdateLastWrite(Server server) | |||
{ | |||
//TODO: combine this part of data with ICMP statics | |||
} | |||
public void UpdateLatency(Server server, TimeSpan latency) | |||
{ | |||
//TODO: combine this part of data with ICMP statics | |||
} | |||
} | |||
} |
@@ -13,7 +13,7 @@ namespace Shadowsocks.Controller.Strategy | |||
_strategies = new List<IStrategy>(); | |||
_strategies.Add(new BalancingStrategy(controller)); | |||
_strategies.Add(new HighAvailabilityStrategy(controller)); | |||
_strategies.Add(new SimplyChooseByStatisticsStrategy(controller)); | |||
_strategies.Add(new StatisticsStrategy(controller)); | |||
// TODO: load DLL plugins | |||
} | |||
public IList<IStrategy> GetStrategies() | |||
@@ -6,21 +6,23 @@ namespace Shadowsocks.Controller | |||
{ | |||
class AutoStartup | |||
{ | |||
static string Key = "Shadowsocks_" + Application.StartupPath.GetHashCode(); | |||
public static bool Set(bool enabled) | |||
{ | |||
RegistryKey runKey = null; | |||
try | |||
{ | |||
string path = Application.ExecutablePath; | |||
RegistryKey runKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Run", true); | |||
runKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Run", true); | |||
if (enabled) | |||
{ | |||
runKey.SetValue("Shadowsocks", path); | |||
runKey.SetValue(Key, path); | |||
} | |||
else | |||
{ | |||
runKey.DeleteValue("Shadowsocks"); | |||
runKey.DeleteValue(Key); | |||
} | |||
runKey.Close(); | |||
return true; | |||
} | |||
catch (Exception e) | |||
@@ -28,20 +30,39 @@ namespace Shadowsocks.Controller | |||
Logging.LogUsefulException(e); | |||
return false; | |||
} | |||
finally | |||
{ | |||
if (runKey != null) | |||
{ | |||
try { runKey.Close(); } | |||
catch (Exception e) | |||
{ Logging.LogUsefulException(e); } | |||
} | |||
} | |||
} | |||
public static bool Check() | |||
{ | |||
RegistryKey runKey = null; | |||
try | |||
{ | |||
string path = Application.ExecutablePath; | |||
RegistryKey runKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Run"); | |||
runKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Run", true); | |||
string[] runList = runKey.GetValueNames(); | |||
runKey.Close(); | |||
foreach (string item in runList) | |||
{ | |||
if (item.Equals("Shadowsocks")) | |||
if (item.Equals(Key)) | |||
return true; | |||
else if (item.Equals("Shadowsocks")) // Compatibility with older versions | |||
{ | |||
string value = Convert.ToString(runKey.GetValue(item)); | |||
if (path.Equals(value, StringComparison.InvariantCultureIgnoreCase)) | |||
{ | |||
runKey.DeleteValue(item); | |||
runKey.SetValue(Key, path); | |||
return true; | |||
} | |||
} | |||
} | |||
return false; | |||
} | |||
@@ -50,6 +71,15 @@ namespace Shadowsocks.Controller | |||
Logging.LogUsefulException(e); | |||
return false; | |||
} | |||
finally | |||
{ | |||
if (runKey != null) | |||
{ | |||
try { runKey.Close(); } | |||
catch(Exception e) | |||
{ Logging.LogUsefulException(e); } | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -10,6 +10,7 @@ PAC=PAC 模式 | |||
Global=全局模式 | |||
Servers=服务器 | |||
Edit Servers...=编辑服务器... | |||
Statistics Config...=统计配置... | |||
Start on Boot=开机启动 | |||
Allow Clients from LAN=允许来自局域网的连接 | |||
Local PAC=使用本地 PAC | |||
@@ -21,6 +22,9 @@ Show QRCode...=显示二维码... | |||
Scan QRCode from Screen...=扫描屏幕上的二维码... | |||
Availability Statistics=统计可用性 | |||
Show Logs...=显示日志... | |||
Updates...=更新... | |||
Check for Updates...=检查更新 | |||
Check for Updates at Startup=启动时检查更新 | |||
About...=关于... | |||
Quit=退出 | |||
Edit Servers=编辑服务器 | |||
@@ -39,6 +43,7 @@ Password=密码 | |||
Encryption=加密 | |||
Proxy Port=代理端口 | |||
Remarks=备注 | |||
Onetime Authentication (Experimental)=一次性认证(实验性) | |||
OK=确定 | |||
Cancel=取消 | |||
New server=未配置的服务器 | |||
@@ -50,10 +55,12 @@ Move D&own=下移(&O) | |||
&File=文件(&F) | |||
&Open Location=在资源管理器中打开(&O) | |||
E&xit=退出(&X) | |||
&Clean logs=清空(&C) | |||
&Font=字体(&F) | |||
&Wrap text=自动换行(&W) | |||
&Top most=置顶(&T) | |||
&View=视图(&V) | |||
&Clean Logs=清空日志(&C) | |||
Change &Font=设置字体(&F) | |||
&Wrap Text=自动换行(&W) | |||
&Top Most=置顶(&T) | |||
&Show Toolbar=显示工具栏(&S) | |||
Log Viewer=日志查看器 | |||
# QRCode Form | |||
@@ -77,7 +84,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=系统代理已启用 | |||
@@ -1,5 +1,5 @@ | |||
listen-address __POLIPO_BIND_IP__:8123 | |||
listen-address __POLIPO_BIND_IP__:__POLIPO_BIND_PORT__ | |||
show-on-task-bar 0 | |||
activity-animation 0 | |||
forward-socks5 / 127.0.0.1:__SOCKS_PORT__ . | |||
hide-console | |||
forward-socks5 / 127.0.0.1:__SOCKS_PORT__ . | |||
hide-console |
@@ -8,19 +8,23 @@ namespace Shadowsocks.Encryption | |||
{ | |||
public const int MAX_INPUT_SIZE = 32768; | |||
protected EncryptorBase(string method, string password) | |||
protected EncryptorBase(string method, string password, bool onetimeauth, bool isudp) | |||
{ | |||
Method = method; | |||
Password = password; | |||
OnetimeAuth = onetimeauth; | |||
IsUDP = isudp; | |||
} | |||
protected string Method; | |||
protected string Password; | |||
protected bool OnetimeAuth; | |||
protected bool IsUDP; | |||
protected byte[] GetPasswordHash() | |||
{ | |||
byte[] inputBytes = Encoding.UTF8.GetBytes(Password); | |||
byte[] hash = MD5.Create().ComputeHash(inputBytes); | |||
byte[] hash = MbedTLS.MD5(inputBytes); | |||
return hash; | |||
} | |||
@@ -8,15 +8,11 @@ namespace Shadowsocks.Encryption | |||
{ | |||
private static Dictionary<string, Type> _registeredEncryptors; | |||
private static Type[] _constructorTypes = new Type[] { typeof(string), typeof(string) }; | |||
private static Type[] _constructorTypes = new Type[] { typeof(string), typeof(string), typeof(bool), typeof(bool) }; | |||
static EncryptorFactory() | |||
{ | |||
_registeredEncryptors = new Dictionary<string, Type>(); | |||
foreach (string method in TableEncryptor.SupportedCiphers()) | |||
{ | |||
_registeredEncryptors.Add(method, typeof(TableEncryptor)); | |||
} | |||
foreach (string method in PolarSSLEncryptor.SupportedCiphers()) | |||
{ | |||
_registeredEncryptors.Add(method, typeof(PolarSSLEncryptor)); | |||
@@ -27,16 +23,16 @@ namespace Shadowsocks.Encryption | |||
} | |||
} | |||
public static IEncryptor GetEncryptor(string method, string password) | |||
public static IEncryptor GetEncryptor(string method, string password, bool onetimeauth, bool isudp) | |||
{ | |||
if (string.IsNullOrEmpty(method)) | |||
{ | |||
method = "table"; | |||
method = "aes-256-cfb"; | |||
} | |||
method = method.ToLowerInvariant(); | |||
Type t = _registeredEncryptors[method]; | |||
ConstructorInfo c = t.GetConstructor(_constructorTypes); | |||
IEncryptor result = (IEncryptor)c.Invoke(new object[] { method, password }); | |||
IEncryptor result = (IEncryptor)c.Invoke(new object[] { method, password, onetimeauth, isudp }); | |||
return result; | |||
} | |||
} | |||
@@ -2,12 +2,24 @@ | |||
using System.Collections.Generic; | |||
using System.Security.Cryptography; | |||
using System.Text; | |||
using System.Net; | |||
namespace Shadowsocks.Encryption | |||
{ | |||
public abstract class IVEncryptor | |||
: EncryptorBase | |||
{ | |||
public const int MAX_KEY_LENGTH = 64; | |||
public const int MAX_IV_LENGTH = 16; | |||
public const int ONETIMEAUTH_FLAG = 0x10; | |||
public const int ADDRTYPE_MASK = 0xF; | |||
public const int ONETIMEAUTH_BYTES = 10; | |||
public const int CLEN_BYTES = 2; | |||
public const int AUTH_BYTES = ONETIMEAUTH_BYTES + CLEN_BYTES; | |||
protected static byte[] tempbuf = new byte[MAX_INPUT_SIZE]; | |||
protected Dictionary<string, int[]> ciphers; | |||
@@ -25,9 +37,11 @@ namespace Shadowsocks.Encryption | |||
protected byte[] _key; | |||
protected int keyLen; | |||
protected int ivLen; | |||
protected uint counter = 0; | |||
protected byte[] _keyBuffer = null; | |||
public IVEncryptor(string method, string password) | |||
: base(method, password) | |||
public IVEncryptor(string method, string password, bool onetimeauth, bool isudp) | |||
: base(method, password, onetimeauth, isudp) | |||
{ | |||
InitKey(method, password); | |||
} | |||
@@ -48,18 +62,22 @@ namespace Shadowsocks.Encryption | |||
} | |||
keyLen = ciphers[_method][0]; | |||
ivLen = ciphers[_method][1]; | |||
if (CachedKeys.ContainsKey(k)) | |||
{ | |||
_key = CachedKeys[k]; | |||
} | |||
else | |||
if (!CachedKeys.ContainsKey(k)) | |||
{ | |||
byte[] passbuf = Encoding.UTF8.GetBytes(password); | |||
_key = new byte[32]; | |||
byte[] iv = new byte[16]; | |||
bytesToKey(passbuf, _key); | |||
CachedKeys[k] = _key; | |||
lock (CachedKeys) | |||
{ | |||
if (!CachedKeys.ContainsKey(k)) | |||
{ | |||
byte[] passbuf = Encoding.UTF8.GetBytes(password); | |||
_key = new byte[32]; | |||
byte[] iv = new byte[16]; | |||
bytesToKey(passbuf, _key); | |||
CachedKeys[k] = _key; | |||
} | |||
} | |||
} | |||
if (_key == null) | |||
_key = CachedKeys[k]; | |||
} | |||
protected void bytesToKey(byte[] password, byte[] key) | |||
@@ -69,16 +87,15 @@ namespace Shadowsocks.Encryption | |||
byte[] md5sum = null; | |||
while (i < key.Length) | |||
{ | |||
MD5 md5 = MD5.Create(); | |||
if (i == 0) | |||
{ | |||
md5sum = md5.ComputeHash(password); | |||
md5sum = MbedTLS.MD5(password); | |||
} | |||
else | |||
{ | |||
md5sum.CopyTo(result, 0); | |||
password.CopyTo(result, md5sum.Length); | |||
md5sum = md5.ComputeHash(result); | |||
md5sum = MbedTLS.MD5(result); | |||
} | |||
md5sum.CopyTo(key, i); | |||
i += md5sum.Length; | |||
@@ -88,7 +105,8 @@ namespace Shadowsocks.Encryption | |||
protected static void randBytes(byte[] buf, int length) | |||
{ | |||
byte[] temp = new byte[length]; | |||
new Random().NextBytes(temp); | |||
RNGCryptoServiceProvider rngServiceProvider = new RNGCryptoServiceProvider(); | |||
rngServiceProvider.GetBytes(temp); | |||
temp.CopyTo(buf, 0); | |||
} | |||
@@ -111,14 +129,122 @@ namespace Shadowsocks.Encryption | |||
protected abstract void cipherUpdate(bool isCipher, int length, byte[] buf, byte[] outbuf); | |||
protected int getHeadLen(byte[] buf, int length) | |||
{ | |||
int len = 0; | |||
int atyp = length > 0 ? (buf[0] & ADDRTYPE_MASK) : 0; | |||
if (atyp == 1) | |||
{ | |||
len = 7; // atyp (1 bytes) + ipv4 (4 bytes) + port (2 bytes) | |||
} | |||
else if (atyp == 3 && length > 1) | |||
{ | |||
int nameLen = buf[1]; | |||
len = 4 + nameLen; // atyp (1 bytes) + name length (1 bytes) + name (n bytes) + port (2 bytes) | |||
} | |||
else if (atyp == 4) | |||
{ | |||
len = 19; // atyp (1 bytes) + ipv6 (16 bytes) + port (2 bytes) | |||
} | |||
if (len == 0 || len > length) | |||
throw new Exception($"invalid header with addr type {atyp}"); | |||
return len; | |||
} | |||
protected byte[] genOnetimeAuthHash(byte[] msg, int msg_len) | |||
{ | |||
byte[] auth = new byte[ONETIMEAUTH_BYTES]; | |||
byte[] hash = new byte[20]; | |||
byte[] auth_key = new byte[MAX_IV_LENGTH + MAX_KEY_LENGTH]; | |||
Buffer.BlockCopy(_encryptIV, 0, auth_key, 0, ivLen); | |||
Buffer.BlockCopy(_key, 0, auth_key, ivLen, keyLen); | |||
Sodium.ss_sha1_hmac_ex(auth_key, (uint)(ivLen + keyLen), | |||
msg, 0, (uint)msg_len, hash); | |||
Buffer.BlockCopy(hash, 0, auth, 0, ONETIMEAUTH_BYTES); | |||
return auth; | |||
} | |||
protected void updateKeyBuffer() | |||
{ | |||
if (_keyBuffer == null) | |||
{ | |||
_keyBuffer = new byte[MAX_IV_LENGTH + 4]; | |||
Buffer.BlockCopy(_encryptIV, 0, _keyBuffer, 0, ivLen); | |||
} | |||
byte[] counter_bytes = BitConverter.GetBytes((uint)IPAddress.HostToNetworkOrder((int)counter)); | |||
Buffer.BlockCopy(counter_bytes, 0, _keyBuffer, ivLen, 4); | |||
counter++; | |||
} | |||
protected byte[] genHash(byte[] buf, int offset, int len) | |||
{ | |||
byte[] hash = new byte[20]; | |||
updateKeyBuffer(); | |||
Sodium.ss_sha1_hmac_ex(_keyBuffer, (uint)_keyBuffer.Length, | |||
buf, offset, (uint)len, hash); | |||
return hash; | |||
} | |||
protected void reactBuffer4TCP(byte[] buf, ref int length) | |||
{ | |||
if (!_encryptIVSent) | |||
{ | |||
int headLen = getHeadLen(buf, length); | |||
int dataLen = length - headLen; | |||
buf[0] |= ONETIMEAUTH_FLAG; | |||
byte[] hash = genOnetimeAuthHash(buf, headLen); | |||
Buffer.BlockCopy(buf, headLen, buf, headLen + ONETIMEAUTH_BYTES + AUTH_BYTES, dataLen); | |||
Buffer.BlockCopy(hash, 0, buf, headLen, ONETIMEAUTH_BYTES); | |||
hash = genHash(buf, headLen + ONETIMEAUTH_BYTES + AUTH_BYTES, dataLen); | |||
Buffer.BlockCopy(hash, 0, buf, headLen + ONETIMEAUTH_BYTES + CLEN_BYTES, ONETIMEAUTH_BYTES); | |||
byte[] lenBytes = BitConverter.GetBytes((ushort)IPAddress.HostToNetworkOrder((short)dataLen)); | |||
Buffer.BlockCopy(lenBytes, 0, buf, headLen + ONETIMEAUTH_BYTES, CLEN_BYTES); | |||
length = headLen + ONETIMEAUTH_BYTES + AUTH_BYTES + dataLen; | |||
} | |||
else | |||
{ | |||
byte[] hash = genHash(buf, 0, length); | |||
Buffer.BlockCopy(buf, 0, buf, AUTH_BYTES, length); | |||
byte[] lenBytes = BitConverter.GetBytes((ushort)IPAddress.HostToNetworkOrder((short)length)); | |||
Buffer.BlockCopy(lenBytes, 0, buf, 0, CLEN_BYTES); | |||
Buffer.BlockCopy(hash, 0, buf, CLEN_BYTES, ONETIMEAUTH_BYTES); | |||
length += AUTH_BYTES; | |||
} | |||
} | |||
protected void reactBuffer4UDP(byte[] buf, ref int length) | |||
{ | |||
buf[0] |= ONETIMEAUTH_FLAG; | |||
byte[] hash = genOnetimeAuthHash(buf, length); | |||
Buffer.BlockCopy(hash, 0, buf, length, ONETIMEAUTH_BYTES); | |||
length += ONETIMEAUTH_BYTES; | |||
} | |||
protected void reactBuffer(byte[] buf, ref int length) | |||
{ | |||
if (OnetimeAuth && ivLen > 0) | |||
{ | |||
if (!IsUDP) | |||
{ | |||
reactBuffer4TCP(buf, ref length); | |||
} | |||
else | |||
{ | |||
reactBuffer4UDP(buf, ref length); | |||
} | |||
} | |||
} | |||
public override void Encrypt(byte[] buf, int length, byte[] outbuf, out int outlength) | |||
{ | |||
if (!_encryptIVSent) | |||
{ | |||
_encryptIVSent = true; | |||
randBytes(outbuf, ivLen); | |||
initCipher(outbuf, true); | |||
outlength = length + ivLen; | |||
reactBuffer(buf, ref length); | |||
_encryptIVSent = true; | |||
lock (tempbuf) | |||
{ | |||
cipherUpdate(true, length, buf, tempbuf); | |||
@@ -128,6 +254,7 @@ namespace Shadowsocks.Encryption | |||
} | |||
else | |||
{ | |||
reactBuffer(buf, ref length); | |||
outlength = length; | |||
cipherUpdate(true, length, buf, outbuf); | |||
} | |||
@@ -153,5 +280,6 @@ namespace Shadowsocks.Encryption | |||
cipherUpdate(false, length, buf, outbuf); | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,65 @@ | |||
using System; | |||
using System.IO; | |||
using System.Runtime.InteropServices; | |||
using Shadowsocks.Controller; | |||
using Shadowsocks.Properties; | |||
using Shadowsocks.Util; | |||
namespace Shadowsocks.Encryption | |||
{ | |||
public class MbedTLS | |||
{ | |||
const string DLLNAME = "libsscrypto"; | |||
static MbedTLS() | |||
{ | |||
string dllPath = Utils.GetTempPath("libsscrypto.dll"); | |||
try | |||
{ | |||
FileManager.UncompressFile(dllPath, Resources.libsscrypto_dll); | |||
} | |||
catch (IOException) | |||
{ | |||
} | |||
catch (Exception e) | |||
{ | |||
Console.WriteLine(e.ToString()); | |||
} | |||
LoadLibrary(dllPath); | |||
} | |||
[DllImport("Kernel32.dll")] | |||
private static extern IntPtr LoadLibrary(string path); | |||
public const int MD5_CTX_SIZE = 88; | |||
public static byte[] MD5(byte[] input) | |||
{ | |||
IntPtr ctx = Marshal.AllocHGlobal(MD5_CTX_SIZE); | |||
byte[] output = new byte[16]; | |||
MbedTLS.md5_init(ctx); | |||
MbedTLS.md5_starts(ctx); | |||
MbedTLS.md5_update(ctx, input, (uint)input.Length); | |||
MbedTLS.md5_finish(ctx, output); | |||
MbedTLS.md5_free(ctx); | |||
Marshal.FreeHGlobal(ctx); | |||
return output; | |||
} | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public extern static void md5_init(IntPtr ctx); | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public extern static void md5_free(IntPtr ctx); | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public extern static void md5_starts(IntPtr ctx); | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public extern static void md5_update(IntPtr ctx, byte[] input, uint ilen); | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public extern static void md5_finish(IntPtr ctx, byte[] output); | |||
} | |||
} |
@@ -1,11 +1,10 @@ | |||
using Shadowsocks.Controller; | |||
using Shadowsocks.Properties; | |||
using Shadowsocks.Util; | |||
using System; | |||
using System.Collections.Generic; | |||
using System; | |||
using System.IO; | |||
using System.Runtime.InteropServices; | |||
using System.Text; | |||
using Shadowsocks.Controller; | |||
using Shadowsocks.Properties; | |||
using Shadowsocks.Util; | |||
namespace Shadowsocks.Encryption | |||
{ | |||
@@ -19,8 +18,7 @@ namespace Shadowsocks.Encryption | |||
static PolarSSL() | |||
{ | |||
string tempPath = Utils.GetTempPath(); | |||
string dllPath = tempPath + "/libsscrypto.dll"; | |||
string dllPath = Utils.GetTempPath("libsscrypto.dll"); | |||
try | |||
{ | |||
FileManager.UncompressFile(dllPath, Resources.libsscrypto_dll); | |||
@@ -30,7 +28,7 @@ namespace Shadowsocks.Encryption | |||
} | |||
catch (Exception e) | |||
{ | |||
Console.WriteLine(e.ToString()); | |||
Logging.LogUsefulException(e); | |||
} | |||
LoadLibrary(dllPath); | |||
} | |||
@@ -63,6 +61,5 @@ namespace Shadowsocks.Encryption | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public extern static int arc4_crypt(IntPtr ctx, int length, byte[] input, byte[] output); | |||
} | |||
} |
@@ -16,8 +16,8 @@ namespace Shadowsocks.Encryption | |||
private IntPtr _encryptCtx = IntPtr.Zero; | |||
private IntPtr _decryptCtx = IntPtr.Zero; | |||
public PolarSSLEncryptor(string method, string password) | |||
: base(method, password) | |||
public PolarSSLEncryptor(string method, string password, bool onetimeauth, bool isudp) | |||
: base(method, password, onetimeauth, isudp) | |||
{ | |||
InitKey(method, password); | |||
} | |||
@@ -26,7 +26,6 @@ namespace Shadowsocks.Encryption | |||
{"aes-128-cfb", new int[]{16, 16, CIPHER_AES, PolarSSL.AES_CTX_SIZE}}, | |||
{"aes-192-cfb", new int[]{24, 16, CIPHER_AES, PolarSSL.AES_CTX_SIZE}}, | |||
{"aes-256-cfb", new int[]{32, 16, CIPHER_AES, PolarSSL.AES_CTX_SIZE}}, | |||
{"rc4", new int[]{16, 0, CIPHER_RC4, PolarSSL.ARC4_CTX_SIZE}}, | |||
{"rc4-md5", new int[]{16, 16, CIPHER_RC4, PolarSSL.ARC4_CTX_SIZE}}, | |||
}; | |||
@@ -61,7 +60,7 @@ namespace Shadowsocks.Encryption | |||
realkey = new byte[keyLen]; | |||
Array.Copy(_key, 0, temp, 0, keyLen); | |||
Array.Copy(iv, 0, temp, keyLen, ivLen); | |||
realkey = MD5.Create().ComputeHash(temp); | |||
realkey = MbedTLS.MD5(temp); | |||
} | |||
else | |||
{ | |||
@@ -1,11 +1,10 @@ | |||
using Shadowsocks.Controller; | |||
using Shadowsocks.Properties; | |||
using Shadowsocks.Util; | |||
using System; | |||
using System.Collections.Generic; | |||
using System; | |||
using System.IO; | |||
using System.Runtime.InteropServices; | |||
using System.Text; | |||
using Shadowsocks.Controller; | |||
using Shadowsocks.Properties; | |||
using Shadowsocks.Util; | |||
namespace Shadowsocks.Encryption | |||
{ | |||
@@ -15,19 +14,17 @@ namespace Shadowsocks.Encryption | |||
static Sodium() | |||
{ | |||
string tempPath = Utils.GetTempPath(); | |||
string dllPath = tempPath + "/libsscrypto.dll"; | |||
string dllPath = Utils.GetTempPath("libsscrypto.dll"); | |||
try | |||
{ | |||
FileManager.UncompressFile(dllPath, Resources.libsscrypto_dll); | |||
LoadLibrary(dllPath); | |||
} | |||
catch (IOException) | |||
{ | |||
} | |||
catch (Exception e) | |||
{ | |||
Console.WriteLine(e.ToString()); | |||
Logging.LogUsefulException(e); | |||
} | |||
LoadLibrary(dllPath); | |||
} | |||
@@ -36,9 +33,18 @@ namespace Shadowsocks.Encryption | |||
private static extern IntPtr LoadLibrary(string path); | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public extern static void crypto_stream_salsa20_xor_ic(byte[] c, byte[] m, ulong mlen, byte[] n, ulong ic, byte[] k); | |||
public extern static int crypto_stream_salsa20_xor_ic(byte[] c, byte[] m, ulong mlen, byte[] n, ulong ic, byte[] k); | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public extern static void crypto_stream_chacha20_xor_ic(byte[] c, byte[] m, ulong mlen, byte[] n, ulong ic, byte[] k); | |||
public extern static int crypto_stream_chacha20_xor_ic(byte[] c, byte[] m, ulong mlen, byte[] n, ulong ic, byte[] k); | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public extern static int crypto_stream_chacha20_ietf_xor_ic(byte[] c, byte[] m, ulong mlen, byte[] n, uint ic, byte[] k); | |||
[DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] | |||
public extern static void ss_sha1_hmac_ex(byte[] key, uint keylen, | |||
byte[] input, int ioff, uint ilen, | |||
byte[] output); | |||
} | |||
} | |||
@@ -10,6 +10,7 @@ namespace Shadowsocks.Encryption | |||
{ | |||
const int CIPHER_SALSA20 = 1; | |||
const int CIPHER_CHACHA20 = 2; | |||
const int CIPHER_CHACHA20_IETF = 3; | |||
const int SODIUM_BLOCK_SIZE = 64; | |||
@@ -20,8 +21,8 @@ namespace Shadowsocks.Encryption | |||
protected ulong _encryptIC; | |||
protected ulong _decryptIC; | |||
public SodiumEncryptor(string method, string password) | |||
: base(method, password) | |||
public SodiumEncryptor(string method, string password, bool onetimeauth, bool isudp) | |||
: base(method, password, onetimeauth, isudp) | |||
{ | |||
InitKey(method, password); | |||
} | |||
@@ -29,6 +30,7 @@ namespace Shadowsocks.Encryption | |||
private static Dictionary<string, int[]> _ciphers = new Dictionary<string, int[]> { | |||
{"salsa20", new int[]{32, 8, CIPHER_SALSA20, PolarSSL.AES_CTX_SIZE}}, | |||
{"chacha20", new int[]{32, 8, CIPHER_CHACHA20, PolarSSL.AES_CTX_SIZE}}, | |||
{"chacha20-ietf", new int[]{32, 12, CIPHER_CHACHA20_IETF, PolarSSL.AES_CTX_SIZE}}, | |||
}; | |||
protected override Dictionary<string, int[]> getCiphers() | |||
@@ -75,6 +77,9 @@ namespace Shadowsocks.Encryption | |||
case CIPHER_CHACHA20: | |||
Sodium.crypto_stream_chacha20_xor_ic(sodiumBuf, sodiumBuf, (ulong)(padding + length), iv, ic, _key); | |||
break; | |||
case CIPHER_CHACHA20_IETF: | |||
Sodium.crypto_stream_chacha20_ietf_xor_ic(sodiumBuf, sodiumBuf, (ulong)(padding + length), iv, (uint)ic, _key); | |||
break; | |||
} | |||
Buffer.BlockCopy(sodiumBuf, padding, outbuf, 0, length); | |||
padding += length; | |||
@@ -1,106 +0,0 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
namespace Shadowsocks.Encryption | |||
{ | |||
public class TableEncryptor | |||
: EncryptorBase | |||
{ | |||
public TableEncryptor(string method, string password) | |||
: base(method, password) | |||
{ | |||
byte[] hash = GetPasswordHash(); | |||
// TODO endian | |||
ulong a = BitConverter.ToUInt64(hash, 0); | |||
for (int i = 0; i < 256; i++) | |||
{ | |||
_encryptTable[i] = (byte)i; | |||
} | |||
for (int i = 1; i < 1024; i++) | |||
{ | |||
_encryptTable = MergeSort(_encryptTable, a, i); | |||
} | |||
for (int i = 0; i < 256; i++) | |||
{ | |||
_decryptTable[_encryptTable[i]] = (byte)i; | |||
} | |||
} | |||
public static List<string> SupportedCiphers() | |||
{ | |||
return new List<string>(new string[]{"table"}); | |||
} | |||
public override void Encrypt(byte[] buf, int length, byte[] outbuf, out int outlength) | |||
{ | |||
byte[] result = new byte[length]; | |||
for (int i = 0; i < length; i++) | |||
{ | |||
outbuf[i] = _encryptTable[buf[i]]; | |||
} | |||
outlength = length; | |||
} | |||
public override void Decrypt(byte[] buf, int length, byte[] outbuf, out int outlength) | |||
{ | |||
byte[] result = new byte[length]; | |||
for (int i = 0; i < length; i++) | |||
{ | |||
outbuf[i] = _decryptTable[buf[i]]; | |||
} | |||
outlength = length; | |||
} | |||
private readonly byte[] _encryptTable = new byte[256]; | |||
private readonly byte[] _decryptTable = new byte[256]; | |||
private static long Compare(byte x, byte y, ulong a, int i) | |||
{ | |||
return (long)(a % (ulong)(x + i)) - (long)(a % (ulong)(y + i)); | |||
} | |||
private byte[] MergeSort(byte[] array, ulong a, int j) | |||
{ | |||
if (array.Length == 1) | |||
{ | |||
return array; | |||
} | |||
int middle = array.Length / 2; | |||
byte[] left = new byte[middle]; | |||
for (int i = 0; i < middle; i++) | |||
{ | |||
left[i] = array[i]; | |||
} | |||
byte[] right = new byte[array.Length - middle]; | |||
for (int i = 0; i < array.Length - middle; i++) | |||
{ | |||
right[i] = array[i + middle]; | |||
} | |||
left = MergeSort(left, a, j); | |||
right = MergeSort(right, a, j); | |||
int leftptr = 0; | |||
int rightptr = 0; | |||
byte[] sorted = new byte[array.Length]; | |||
for (int k = 0; k < array.Length; k++) | |||
{ | |||
if (rightptr == right.Length || ((leftptr < left.Length) && (Compare(left[leftptr], right[rightptr], a, j) <= 0))) | |||
{ | |||
sorted[k] = left[leftptr]; | |||
leftptr++; | |||
} | |||
else if (leftptr == left.Length || ((rightptr < right.Length) && (Compare(right[rightptr], left[leftptr], a, j)) <= 0)) | |||
{ | |||
sorted[k] = right[rightptr]; | |||
rightptr++; | |||
} | |||
} | |||
return sorted; | |||
} | |||
public override void Dispose() | |||
{ | |||
} | |||
} | |||
} |
@@ -0,0 +1,5 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<Weavers> | |||
<Costura/> | |||
</Weavers> |
@@ -1,9 +1,9 @@ | |||
using Shadowsocks.Controller; | |||
using System; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.IO; | |||
using System.Text; | |||
using System.Windows.Forms; | |||
using Shadowsocks.Controller; | |||
using Newtonsoft.Json; | |||
namespace Shadowsocks.Model | |||
{ | |||
@@ -23,19 +23,17 @@ namespace Shadowsocks.Model | |||
public string pacUrl; | |||
public bool useOnlinePac; | |||
public bool availabilityStatistics; | |||
public bool autoCheckUpdate; | |||
public LogViewerConfig logViewer; | |||
private static string CONFIG_FILE = "gui-config.json"; | |||
public Server GetCurrentServer() | |||
{ | |||
if (index >= 0 && index < configs.Count) | |||
{ | |||
return configs[index]; | |||
} | |||
else | |||
{ | |||
return GetDefaultServer(); | |||
} | |||
} | |||
public static void CheckServer(Server server) | |||
@@ -50,32 +48,24 @@ namespace Shadowsocks.Model | |||
try | |||
{ | |||
string configContent = File.ReadAllText(CONFIG_FILE); | |||
Configuration config = SimpleJson.SimpleJson.DeserializeObject<Configuration>(configContent, new JsonSerializerStrategy()); | |||
Configuration config = JsonConvert.DeserializeObject<Configuration>(configContent); | |||
config.isDefault = false; | |||
if (config.localPort == 0) | |||
{ | |||
config.localPort = 1080; | |||
} | |||
if (config.index == -1) | |||
{ | |||
if (config.strategy == null) | |||
{ | |||
config.index = 0; | |||
} | |||
} | |||
if (config.index == -1 && config.strategy == null) | |||
config.index = 0; | |||
return config; | |||
} | |||
catch (Exception e) | |||
{ | |||
if (!(e is FileNotFoundException)) | |||
{ | |||
Console.WriteLine(e); | |||
} | |||
Logging.LogUsefulException(e); | |||
return new Configuration | |||
{ | |||
index = 0, | |||
isDefault = true, | |||
localPort = 1080, | |||
autoCheckUpdate = true, | |||
configs = new List<Server>() | |||
{ | |||
GetDefaultServer() | |||
@@ -87,26 +77,17 @@ namespace Shadowsocks.Model | |||
public static void Save(Configuration config) | |||
{ | |||
if (config.index >= config.configs.Count) | |||
{ | |||
config.index = config.configs.Count - 1; | |||
} | |||
if (config.index < -1) | |||
{ | |||
config.index = -1; | |||
} | |||
if (config.index == -1) | |||
{ | |||
if (config.strategy == null) | |||
{ | |||
config.index = 0; | |||
} | |||
} | |||
if (config.index == -1 && config.strategy == null) | |||
config.index = 0; | |||
config.isDefault = false; | |||
try | |||
{ | |||
using (StreamWriter sw = new StreamWriter(File.Open(CONFIG_FILE, FileMode.Create))) | |||
{ | |||
string jsonString = SimpleJson.SimpleJson.SerializeObject(config); | |||
string jsonString = JsonConvert.SerializeObject(config, Formatting.Indented); | |||
sw.Write(jsonString); | |||
sw.Flush(); | |||
} | |||
@@ -125,55 +106,32 @@ namespace Shadowsocks.Model | |||
private static void Assert(bool condition) | |||
{ | |||
if (!condition) | |||
{ | |||
throw new Exception(I18N.GetString("assertion failure")); | |||
} | |||
} | |||
public static void CheckPort(int port) | |||
{ | |||
if (port <= 0 || port > 65535) | |||
{ | |||
throw new ArgumentException(I18N.GetString("Port out of range")); | |||
} | |||
} | |||
public static void CheckLocalPort(int port) | |||
{ | |||
CheckPort(port); | |||
if (port == 8123) | |||
{ | |||
throw new ArgumentException(I18N.GetString("Port can't be 8123")); | |||
} | |||
} | |||
private static void CheckPassword(string password) | |||
{ | |||
if (string.IsNullOrEmpty(password)) | |||
{ | |||
throw new ArgumentException(I18N.GetString("Password can not be blank")); | |||
} | |||
} | |||
private static void CheckServer(string server) | |||
{ | |||
if (string.IsNullOrEmpty(server)) | |||
{ | |||
throw new ArgumentException(I18N.GetString("Server IP can not be blank")); | |||
} | |||
} | |||
private class JsonSerializerStrategy : SimpleJson.PocoJsonSerializerStrategy | |||
{ | |||
// convert string to int | |||
public override object DeserializeObject(object value, Type type) | |||
{ | |||
if (type == typeof(Int32) && value.GetType() == typeof(string)) | |||
{ | |||
return Int32.Parse(value.ToString()); | |||
} | |||
return base.DeserializeObject(value, type); | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,103 @@ | |||
using Shadowsocks.View; | |||
using System; | |||
using System.Drawing; | |||
using System.Windows.Forms; | |||
namespace Shadowsocks.Model | |||
{ | |||
[Serializable] | |||
public class LogViewerConfig | |||
{ | |||
public string fontName; | |||
public float fontSize; | |||
public string bgColor; | |||
public string textColor; | |||
public bool topMost; | |||
public bool wrapText; | |||
public bool toolbarShown; | |||
public int width; | |||
public int height; | |||
public int top; | |||
public int left; | |||
public LogViewerConfig() | |||
{ | |||
fontName = "Consolas"; | |||
fontSize = 8; | |||
bgColor = "black"; | |||
textColor = "white"; | |||
topMost = false; | |||
wrapText = false; | |||
toolbarShown = false; | |||
width = 600; | |||
height = 400; | |||
left = GetBestLeft(); | |||
top = GetBestTop(); | |||
} | |||
// Use GetBestTop() and GetBestLeft() to ensure the log viwer form can be always display IN screen. | |||
public int GetBestLeft() | |||
{ | |||
width = (width >= 400) ? width : 400; // set up the minimum size | |||
return Screen.PrimaryScreen.WorkingArea.Width - width; | |||
} | |||
public int GetBestTop() | |||
{ | |||
height = (height >= 200) ? height : 200; // set up the minimum size | |||
return Screen.PrimaryScreen.WorkingArea.Height - height; | |||
} | |||
public Font GetFont() | |||
{ | |||
try | |||
{ | |||
return new Font(fontName, fontSize, FontStyle.Regular); | |||
} | |||
catch (Exception) | |||
{ | |||
return new Font("Console", 8F); | |||
} | |||
} | |||
public void SetFont(Font font) | |||
{ | |||
fontName = font.Name; | |||
fontSize = font.Size; | |||
} | |||
public Color GetBackgroundColor() | |||
{ | |||
try | |||
{ | |||
return ColorTranslator.FromHtml(bgColor); | |||
} | |||
catch (Exception) | |||
{ | |||
return ColorTranslator.FromHtml("black"); | |||
} | |||
} | |||
public void SetBackgroundColor(Color color) | |||
{ | |||
bgColor = ColorTranslator.ToHtml(color); | |||
} | |||
public Color GetTextColor() | |||
{ | |||
try | |||
{ | |||
return ColorTranslator.FromHtml(textColor); | |||
} | |||
catch (Exception) | |||
{ | |||
return ColorTranslator.FromHtml("white"); | |||
} | |||
} | |||
public void SetTextColor(Color color) | |||
{ | |||
textColor = ColorTranslator.ToHtml(color); | |||
} | |||
} | |||
} |
@@ -1,12 +1,9 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Text; | |||
using System.IO; | |||
using System.Diagnostics; | |||
using SimpleJson; | |||
using Shadowsocks.Controller; | |||
using System.Text.RegularExpressions; | |||
using Shadowsocks.Controller; | |||
namespace Shadowsocks.Model | |||
{ | |||
[Serializable] | |||
@@ -17,6 +14,7 @@ namespace Shadowsocks.Model | |||
public string password; | |||
public string method; | |||
public string remarks; | |||
public bool auth; | |||
public override int GetHashCode() | |||
{ | |||
@@ -26,7 +24,7 @@ namespace Shadowsocks.Model | |||
public override bool Equals(object obj) | |||
{ | |||
Server o2 = (Server)obj; | |||
return this.server == o2.server && this.server_port == o2.server_port; | |||
return server == o2.server && server_port == o2.server_port; | |||
} | |||
public string FriendlyName() | |||
@@ -47,11 +45,12 @@ namespace Shadowsocks.Model | |||
public Server() | |||
{ | |||
this.server = ""; | |||
this.server_port = 8388; | |||
this.method = "aes-256-cfb"; | |||
this.password = ""; | |||
this.remarks = ""; | |||
server = ""; | |||
server_port = 8388; | |||
method = "aes-256-cfb"; | |||
password = ""; | |||
remarks = ""; | |||
auth = false; | |||
} | |||
public Server(string ssURL) : this() | |||
@@ -63,7 +62,7 @@ namespace Shadowsocks.Model | |||
{ | |||
try | |||
{ | |||
bytes = System.Convert.FromBase64String(base64); | |||
bytes = Convert.FromBase64String(base64); | |||
} | |||
catch (FormatException) | |||
{ | |||
@@ -81,13 +80,15 @@ namespace Shadowsocks.Model | |||
string afterAt = data.Substring(indexLastAt + 1); | |||
int indexLastColon = afterAt.LastIndexOf(':'); | |||
this.server_port = int.Parse(afterAt.Substring(indexLastColon + 1)); | |||
this.server = afterAt.Substring(0, indexLastColon); | |||
server_port = int.Parse(afterAt.Substring(indexLastColon + 1)); | |||
server = afterAt.Substring(0, indexLastColon); | |||
string beforeAt = data.Substring(0, indexLastAt); | |||
string[] parts = beforeAt.Split(new[] { ':' }); | |||
this.method = parts[0]; | |||
this.password = parts[1]; | |||
method = parts[0]; | |||
password = parts[1]; | |||
//TODO: read one_time_auth | |||
} | |||
catch (IndexOutOfRangeException) | |||
{ | |||
@@ -0,0 +1,106 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.IO; | |||
using System.Linq; | |||
using System.Reflection; | |||
using Newtonsoft.Json; | |||
using Shadowsocks.Controller; | |||
namespace Shadowsocks.Model | |||
{ | |||
[Serializable] | |||
public class StatisticsStrategyConfiguration | |||
{ | |||
public static readonly string ID = "com.shadowsocks.strategy.statistics"; | |||
private bool _statisticsEnabled = true; | |||
private bool _byIsp = false; | |||
private bool _byHourOfDay = false; | |||
private int _choiceKeptMinutes = 10; | |||
private int _dataCollectionMinutes = 10; | |||
private int _repeatTimesNum = 4; | |||
private const string ConfigFile = "statistics-config.json"; | |||
public static StatisticsStrategyConfiguration Load() | |||
{ | |||
try | |||
{ | |||
var content = File.ReadAllText(ConfigFile); | |||
var configuration = JsonConvert.DeserializeObject<StatisticsStrategyConfiguration>(content); | |||
return configuration; | |||
} | |||
catch (FileNotFoundException) | |||
{ | |||
var configuration = new StatisticsStrategyConfiguration(); | |||
Save(configuration); | |||
return configuration; | |||
} | |||
catch (Exception e) | |||
{ | |||
Logging.LogUsefulException(e); | |||
return new StatisticsStrategyConfiguration(); | |||
} | |||
} | |||
public static void Save(StatisticsStrategyConfiguration configuration) | |||
{ | |||
try | |||
{ | |||
var content = JsonConvert.SerializeObject(configuration, Formatting.Indented); | |||
File.WriteAllText(ConfigFile, content); | |||
} | |||
catch (Exception e) | |||
{ | |||
Logging.LogUsefulException(e); | |||
} | |||
} | |||
public Dictionary<string, float> Calculations; | |||
public StatisticsStrategyConfiguration() | |||
{ | |||
var availabilityStatisticsType = typeof(AvailabilityStatistics); | |||
var statisticsData = availabilityStatisticsType.GetNestedType("StatisticsData"); | |||
var properties = statisticsData.GetFields(BindingFlags.Instance | BindingFlags.Public); | |||
Calculations = properties.ToDictionary(p => p.Name, _ => (float)0); | |||
} | |||
public bool StatisticsEnabled | |||
{ | |||
get { return _statisticsEnabled; } | |||
set { _statisticsEnabled = value; } | |||
} | |||
public bool ByIsp | |||
{ | |||
get { return _byIsp; } | |||
set { _byIsp = value; } | |||
} | |||
public bool ByHourOfDay | |||
{ | |||
get { return _byHourOfDay; } | |||
set { _byHourOfDay = value; } | |||
} | |||
public int ChoiceKeptMinutes | |||
{ | |||
get { return _choiceKeptMinutes; } | |||
set { _choiceKeptMinutes = value; } | |||
} | |||
public int DataCollectionMinutes | |||
{ | |||
get { return _dataCollectionMinutes; } | |||
set { _dataCollectionMinutes = value; } | |||
} | |||
public int RepeatTimesNum | |||
{ | |||
get { return _repeatTimesNum; } | |||
set { _repeatTimesNum = value; } | |||
} | |||
} | |||
} |
@@ -1,13 +1,13 @@ | |||
using Shadowsocks.Controller; | |||
using Shadowsocks.Properties; | |||
using Shadowsocks.View; | |||
using System; | |||
using System.Collections.Generic; | |||
using System; | |||
using System.Diagnostics; | |||
using System.IO; | |||
using System.Threading; | |||
using System.Windows.Forms; | |||
using Shadowsocks.Controller; | |||
using Shadowsocks.Util; | |||
using Shadowsocks.View; | |||
namespace Shadowsocks | |||
{ | |||
static class Program | |||
@@ -18,7 +18,7 @@ namespace Shadowsocks | |||
[STAThread] | |||
static void Main() | |||
{ | |||
Util.Utils.ReleaseMemory(true); | |||
Utils.ReleaseMemory(true); | |||
using (Mutex mutex = new Mutex(false, "Global\\Shadowsocks_" + Application.StartupPath.GetHashCode())) | |||
{ | |||
Application.EnableVisualStyles(); | |||
@@ -37,15 +37,19 @@ namespace Shadowsocks | |||
return; | |||
} | |||
Directory.SetCurrentDirectory(Application.StartupPath); | |||
#if !DEBUG | |||
#if DEBUG | |||
Logging.OpenLogFile(); | |||
// truncate privoxy log file while debugging | |||
string privoxyLogFilename = Utils.GetTempPath("privoxy.log"); | |||
if (File.Exists(privoxyLogFilename)) | |||
using (new FileStream(privoxyLogFilename, FileMode.Truncate)) { } | |||
#else | |||
Logging.OpenLogFile(); | |||
#endif | |||
ShadowsocksController controller = new ShadowsocksController(); | |||
MenuViewController viewController = new MenuViewController(controller); | |||
controller.Start(); | |||
Application.Run(); | |||
} | |||
} | |||
@@ -0,0 +1,10 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<!-- | |||
This file is automatically generated by Visual Studio .Net. It is | |||
used to store generic object data source configuration information. | |||
Renaming the file extension or editing the content of this file may | |||
cause the file to be unrecognizable by the program. | |||
--> | |||
<GenericObjectDataSource DisplayName="StatisticsStrategyConfiguration" Version="1.0" xmlns="urn:schemas-microsoft-com:xml-msdatasource"> | |||
<TypeInfo>Shadowsocks.Model.StatisticsStrategyConfiguration, Shadowsocks, Version=2.5.2.0, Culture=neutral, PublicKeyToken=null</TypeInfo> | |||
</GenericObjectDataSource> |
@@ -10,8 +10,8 @@ | |||
namespace Shadowsocks.Properties { | |||
using System; | |||
/// <summary> | |||
/// A strongly-typed resource class, for looking up localized strings, etc. | |||
/// </summary> | |||
@@ -31,7 +31,7 @@ namespace Shadowsocks.Properties { | |||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] | |||
internal Resources() { | |||
} | |||
/// <summary> | |||
/// Returns the cached ResourceManager instance used by this class. | |||
/// </summary> | |||
@@ -45,7 +45,7 @@ namespace Shadowsocks.Properties { | |||
return resourceMan; | |||
} | |||
} | |||
/// <summary> | |||
/// Overrides the current thread's CurrentUICulture property for all | |||
/// resource lookups using this strongly typed resource class. | |||
@@ -59,7 +59,7 @@ namespace Shadowsocks.Properties { | |||
resourceCulture = value; | |||
} | |||
} | |||
/// <summary> | |||
/// Looks up a localized resource of type System.Byte[]. | |||
/// </summary> | |||
@@ -69,39 +69,37 @@ namespace Shadowsocks.Properties { | |||
return ((byte[])(obj)); | |||
} | |||
} | |||
/// <summary> | |||
/// Looks up a localized string similar to # translation for Simplified Chinese | |||
/// | |||
///Shadowsocks=Shadowsocks | |||
/// | |||
///# Menu items | |||
/// | |||
///Enable System Proxy=启用系统代理 | |||
///Mode=系统代理模式 | |||
///PAC=PAC 模式 | |||
///Global=全局模式 | |||
///Servers=服务器 | |||
///Edit Servers...=编辑服务器... | |||
///Start on Boot=开机启动 | |||
///Allow Clients from LAN=允许来自局域网的连接 | |||
///Local PAC=使用本地 PAC | |||
///Online PAC=使用在线 PAC | |||
///Edit Local PAC File...=编辑本地 PAC 文件... | |||
///Update Local PAC from GFWList=从 GFWList 更新本地 PAC | |||
///Edit User Rule for GFWList...=编辑 GFWList 的用户规则... | |||
///Show QRCode...=显示二维码... | |||
///Scan QRCode from Screen...=扫描屏幕上的二维码... | |||
///Show Logs...=显示日志... | |||
///About...=关于... | |||
///Quit=退出 [rest of string was truncated]";. | |||
/// Looks up a localized string similar to # translation for Simplified Chinese | |||
/// | |||
///Shadowsocks=Shadowsocks | |||
/// | |||
///# Menu items | |||
/// | |||
///Enable System Proxy=启用系统代理 | |||
///Mode=系统代理模式 | |||
///PAC=PAC 模式 | |||
///Global=全局模式 | |||
///Servers=服务器 | |||
///Edit Servers...=编辑服务器... | |||
///Start on Boot=开机启动 | |||
///Allow Clients from LAN=允许来自局域网的连接 | |||
///Local PAC=使用本地 PAC | |||
///Online PAC=使用在线 PAC | |||
///Edit Local PAC File...=编辑本地 PAC 文件... | |||
///Update Local PAC from GFWList=从 GFWList 更新本地 PAC | |||
///Edit User Rule for GFWList...=编辑 GFWList 的用户规则... | |||
///Show QRCode...=显示二维码... | |||
///Scan QRCode from Screen...=扫描屏幕上的二维码... | |||
///Availability Statistic [rest of string was truncated]";. | |||
/// </summary> | |||
internal static string cn { | |||
get { | |||
return ResourceManager.GetString("cn", resourceCulture); | |||
} | |||
} | |||
/// <summary> | |||
/// Looks up a localized resource of type System.Byte[]. | |||
/// </summary> | |||
@@ -111,7 +109,7 @@ namespace Shadowsocks.Properties { | |||
return ((byte[])(obj)); | |||
} | |||
} | |||
/// <summary> | |||
/// Looks up a localized resource of type System.Byte[]. | |||
/// </summary> | |||
@@ -121,7 +119,7 @@ namespace Shadowsocks.Properties { | |||
return ((byte[])(obj)); | |||
} | |||
} | |||
/// <summary> | |||
/// Looks up a localized string similar to listen-address __POLIPO_BIND_IP__:8123 | |||
///show-on-task-bar 0 | |||
@@ -134,7 +132,7 @@ namespace Shadowsocks.Properties { | |||
return ResourceManager.GetString("privoxy_conf", resourceCulture); | |||
} | |||
} | |||
/// <summary> | |||
/// Looks up a localized resource of type System.Byte[]. | |||
/// </summary> | |||
@@ -144,7 +142,7 @@ namespace Shadowsocks.Properties { | |||
return ((byte[])(obj)); | |||
} | |||
} | |||
/// <summary> | |||
/// Looks up a localized resource of type System.Byte[]. | |||
/// </summary> | |||
@@ -154,7 +152,7 @@ namespace Shadowsocks.Properties { | |||
return ((byte[])(obj)); | |||
} | |||
} | |||
/// <summary> | |||
/// Looks up a localized resource of type System.Drawing.Bitmap. | |||
/// </summary> | |||
@@ -164,7 +162,7 @@ namespace Shadowsocks.Properties { | |||
return ((System.Drawing.Bitmap)(obj)); | |||
} | |||
} | |||
/// <summary> | |||
/// Looks up a localized resource of type System.Drawing.Bitmap. | |||
/// </summary> | |||
@@ -174,7 +172,7 @@ namespace Shadowsocks.Properties { | |||
return ((System.Drawing.Bitmap)(obj)); | |||
} | |||
} | |||
/// <summary> | |||
/// Looks up a localized resource of type System.Drawing.Bitmap. | |||
/// </summary> | |||
@@ -184,7 +182,7 @@ namespace Shadowsocks.Properties { | |||
return ((System.Drawing.Bitmap)(obj)); | |||
} | |||
} | |||
/// <summary> | |||
/// Looks up a localized resource of type System.Drawing.Bitmap. | |||
/// </summary> | |||
@@ -194,7 +192,7 @@ namespace Shadowsocks.Properties { | |||
return ((System.Drawing.Bitmap)(obj)); | |||
} | |||
} | |||
/// <summary> | |||
/// Looks up a localized string similar to ! Put user rules line by line in this file. | |||
///! See https://adblockplus.org/en/filter-cheatsheet | |||
@@ -1,32 +1,48 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Diagnostics; | |||
using System.IO; | |||
using System.IO.Compression; | |||
using System.Runtime.InteropServices; | |||
using System.Text; | |||
using System.Windows.Forms; | |||
using Shadowsocks.Controller; | |||
namespace Shadowsocks.Util | |||
{ | |||
public class Utils | |||
{ | |||
private static string TempPath = null; | |||
// return path to store temporary files | |||
public static string GetTempPath() | |||
{ | |||
if (File.Exists(Application.StartupPath + "\\shadowsocks_portable_mode.txt")) | |||
if (TempPath == null) | |||
{ | |||
try | |||
{ | |||
Directory.CreateDirectory(Application.StartupPath + "\\temp"); | |||
} catch (Exception e) | |||
{ | |||
Console.WriteLine(e); | |||
} | |||
// don't use "/", it will fail when we call explorer /select xxx/temp\xxx.log | |||
return Application.StartupPath + "\\temp"; | |||
if (File.Exists(Path.Combine(Application.StartupPath, "shadowsocks_portable_mode.txt"))) | |||
try | |||
{ | |||
Directory.CreateDirectory(Path.Combine(Application.StartupPath, "temp")); | |||
} | |||
catch (Exception e) | |||
{ | |||
TempPath = Path.GetTempPath(); | |||
Logging.LogUsefulException(e); | |||
} | |||
finally | |||
{ | |||
// don't use "/", it will fail when we call explorer /select xxx/temp\xxx.log | |||
TempPath = Path.Combine(Application.StartupPath, "temp"); | |||
} | |||
else | |||
TempPath = Path.GetTempPath(); | |||
} | |||
return Path.GetTempPath(); | |||
return TempPath; | |||
} | |||
// return a full path with filename combined which pointed to the temporary directory | |||
public static string GetTempPath(string filename) | |||
{ | |||
return Path.Combine(GetTempPath(), filename); | |||
} | |||
public static void ReleaseMemory(bool removePages) | |||
@@ -82,6 +98,33 @@ namespace Shadowsocks.Util | |||
} | |||
} | |||
public static string FormatBandwidth(long n) | |||
{ | |||
float f = n; | |||
string unit = "B"; | |||
if (f > 1024) | |||
{ | |||
f = f / 1024; | |||
unit = "KiB"; | |||
} | |||
if (f > 1024) | |||
{ | |||
f = f / 1024; | |||
unit = "MiB"; | |||
} | |||
if (f > 1024) | |||
{ | |||
f = f / 1024; | |||
unit = "GiB"; | |||
} | |||
if (f > 1024) | |||
{ | |||
f = f / 1024; | |||
unit = "TiB"; | |||
} | |||
return $"{f:0.##}{unit}"; | |||
} | |||
[DllImport("kernel32.dll")] | |||
[return: MarshalAs(UnmanagedType.Bool)] | |||
private static extern bool SetProcessWorkingSetSize(IntPtr process, | |||
@@ -0,0 +1,111 @@ | |||
namespace Shadowsocks.View | |||
{ | |||
partial class CalculationControl | |||
{ | |||
/// <summary> | |||
/// Required designer variable. | |||
/// </summary> | |||
private System.ComponentModel.IContainer components = null; | |||
/// <summary> | |||
/// Clean up any resources being used. | |||
/// </summary> | |||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> | |||
protected override void Dispose(bool disposing) | |||
{ | |||
if (disposing && (components != null)) | |||
{ | |||
components.Dispose(); | |||
} | |||
base.Dispose(disposing); | |||
} | |||
#region Component Designer generated code | |||
/// <summary> | |||
/// Required method for Designer support - do not modify | |||
/// the contents of this method with the code editor. | |||
/// </summary> | |||
private void InitializeComponent() | |||
{ | |||
this.factorNum = new System.Windows.Forms.NumericUpDown(); | |||
this.multiply = new System.Windows.Forms.Label(); | |||
this.plus = new System.Windows.Forms.Label(); | |||
this.valueLabel = new System.Windows.Forms.Label(); | |||
((System.ComponentModel.ISupportInitialize)(this.factorNum)).BeginInit(); | |||
this.SuspendLayout(); | |||
// | |||
// factorNum | |||
// | |||
this.factorNum.DecimalPlaces = 2; | |||
this.factorNum.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); | |||
this.factorNum.Increment = new decimal(new int[] { | |||
1, | |||
0, | |||
0, | |||
131072}); | |||
this.factorNum.Location = new System.Drawing.Point(285, 5); | |||
this.factorNum.Minimum = new decimal(new int[] { | |||
1000, | |||
0, | |||
0, | |||
-2147418112}); | |||
this.factorNum.Name = "factorNum"; | |||
this.factorNum.Size = new System.Drawing.Size(86, 34); | |||
this.factorNum.TabIndex = 6; | |||
// | |||
// multiply | |||
// | |||
this.multiply.AutoSize = true; | |||
this.multiply.Font = new System.Drawing.Font("Segoe UI", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); | |||
this.multiply.Location = new System.Drawing.Point(251, 7); | |||
this.multiply.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0); | |||
this.multiply.Name = "multiply"; | |||
this.multiply.Size = new System.Drawing.Size(26, 28); | |||
this.multiply.TabIndex = 2; | |||
this.multiply.Text = "×"; | |||
// | |||
// plus | |||
// | |||
this.plus.AutoSize = true; | |||
this.plus.Font = new System.Drawing.Font("Segoe UI", 10F); | |||
this.plus.Location = new System.Drawing.Point(5, 7); | |||
this.plus.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0); | |||
this.plus.Name = "plus"; | |||
this.plus.Size = new System.Drawing.Size(26, 28); | |||
this.plus.TabIndex = 3; | |||
this.plus.Text = "+"; | |||
// | |||
// valueLabel | |||
// | |||
this.valueLabel.AutoSize = true; | |||
this.valueLabel.Font = new System.Drawing.Font("Microsoft YaHei", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); | |||
this.valueLabel.Location = new System.Drawing.Point(39, 11); | |||
this.valueLabel.Name = "valueLabel"; | |||
this.valueLabel.Size = new System.Drawing.Size(118, 24); | |||
this.valueLabel.TabIndex = 7; | |||
this.valueLabel.Text = "PackageLoss"; | |||
// | |||
// CalculationControl | |||
// | |||
this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 18F); | |||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; | |||
this.Controls.Add(this.valueLabel); | |||
this.Controls.Add(this.factorNum); | |||
this.Controls.Add(this.multiply); | |||
this.Controls.Add(this.plus); | |||
this.Name = "CalculationControl"; | |||
this.Size = new System.Drawing.Size(380, 46); | |||
((System.ComponentModel.ISupportInitialize)(this.factorNum)).EndInit(); | |||
this.ResumeLayout(false); | |||
this.PerformLayout(); | |||
} | |||
#endregion | |||
private System.Windows.Forms.NumericUpDown factorNum; | |||
private System.Windows.Forms.Label multiply; | |||
private System.Windows.Forms.Label plus; | |||
private System.Windows.Forms.Label valueLabel; | |||
} | |||
} |
@@ -0,0 +1,25 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.ComponentModel; | |||
using System.Drawing; | |||
using System.Data; | |||
using System.Globalization; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Windows.Forms; | |||
namespace Shadowsocks.View | |||
{ | |||
public partial class CalculationControl : UserControl | |||
{ | |||
public CalculationControl(string text, float value) | |||
{ | |||
InitializeComponent(); | |||
valueLabel.Text = text; | |||
factorNum.Value = (decimal) value; | |||
} | |||
public string Value => valueLabel.Text; | |||
public float Factor => (float) factorNum.Value; | |||
} | |||
} |
@@ -0,0 +1,120 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<root> | |||
<!-- | |||
Microsoft ResX Schema | |||
Version 2.0 | |||
The primary goals of this format is to allow a simple XML format | |||
that is mostly human readable. The generation and parsing of the | |||
various data types are done through the TypeConverter classes | |||
associated with the data types. | |||
Example: | |||
... ado.net/XML headers & schema ... | |||
<resheader name="resmimetype">text/microsoft-resx</resheader> | |||
<resheader name="version">2.0</resheader> | |||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> | |||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> | |||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> | |||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> | |||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> | |||
<value>[base64 mime encoded serialized .NET Framework object]</value> | |||
</data> | |||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> | |||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> | |||
<comment>This is a comment</comment> | |||
</data> | |||
There are any number of "resheader" rows that contain simple | |||
name/value pairs. | |||
Each data row contains a name, and value. The row also contains a | |||
type or mimetype. Type corresponds to a .NET class that support | |||
text/value conversion through the TypeConverter architecture. | |||
Classes that don't support this are serialized and stored with the | |||
mimetype set. | |||
The mimetype is used for serialized objects, and tells the | |||
ResXResourceReader how to depersist the object. This is currently not | |||
extensible. For a given mimetype the value must be set accordingly: | |||
Note - application/x-microsoft.net.object.binary.base64 is the format | |||
that the ResXResourceWriter will generate, however the reader can | |||
read any of the formats listed below. | |||
mimetype: application/x-microsoft.net.object.binary.base64 | |||
value : The object must be serialized with | |||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter | |||
: and then encoded with base64 encoding. | |||
mimetype: application/x-microsoft.net.object.soap.base64 | |||
value : The object must be serialized with | |||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter | |||
: and then encoded with base64 encoding. | |||
mimetype: application/x-microsoft.net.object.bytearray.base64 | |||
value : The object must be serialized into a byte array | |||
: using a System.ComponentModel.TypeConverter | |||
: and then encoded with base64 encoding. | |||
--> | |||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> | |||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> | |||
<xsd:element name="root" msdata:IsDataSet="true"> | |||
<xsd:complexType> | |||
<xsd:choice maxOccurs="unbounded"> | |||
<xsd:element name="metadata"> | |||
<xsd:complexType> | |||
<xsd:sequence> | |||
<xsd:element name="value" type="xsd:string" minOccurs="0" /> | |||
</xsd:sequence> | |||
<xsd:attribute name="name" use="required" type="xsd:string" /> | |||
<xsd:attribute name="type" type="xsd:string" /> | |||
<xsd:attribute name="mimetype" type="xsd:string" /> | |||
<xsd:attribute ref="xml:space" /> | |||
</xsd:complexType> | |||
</xsd:element> | |||
<xsd:element name="assembly"> | |||
<xsd:complexType> | |||
<xsd:attribute name="alias" type="xsd:string" /> | |||
<xsd:attribute name="name" type="xsd:string" /> | |||
</xsd:complexType> | |||
</xsd:element> | |||
<xsd:element name="data"> | |||
<xsd:complexType> | |||
<xsd:sequence> | |||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | |||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> | |||
</xsd:sequence> | |||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> | |||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> | |||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> | |||
<xsd:attribute ref="xml:space" /> | |||
</xsd:complexType> | |||
</xsd:element> | |||
<xsd:element name="resheader"> | |||
<xsd:complexType> | |||
<xsd:sequence> | |||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | |||
</xsd:sequence> | |||
<xsd:attribute name="name" type="xsd:string" use="required" /> | |||
</xsd:complexType> | |||
</xsd:element> | |||
</xsd:choice> | |||
</xsd:complexType> | |||
</xsd:element> | |||
</xsd:schema> | |||
<resheader name="resmimetype"> | |||
<value>text/microsoft-resx</value> | |||
</resheader> | |||
<resheader name="version"> | |||
<value>2.0</value> | |||
</resheader> | |||
<resheader name="reader"> | |||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||
</resheader> | |||
<resheader name="writer"> | |||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||
</resheader> | |||
</root> |
@@ -3,14 +3,14 @@ | |||
partial class ConfigForm | |||
{ | |||
/// <summary> | |||
/// 必需的设计器变量。 | |||
/// Required designer variable. | |||
/// </summary> | |||
private System.ComponentModel.IContainer components = null; | |||
/// <summary> | |||
/// 清理所有正在使用的资源。 | |||
/// Clean up any resources being used. | |||
/// </summary> | |||
/// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param> | |||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> | |||
protected override void Dispose(bool disposing) | |||
{ | |||
if (disposing && (components != null)) | |||
@@ -20,11 +20,11 @@ | |||
base.Dispose(disposing); | |||
} | |||
#region Windows 窗体设计器生成的代码 | |||
#region Windows Form Designer generated code | |||
/// <summary> | |||
/// 设计器支持所需的方法 - 不要 | |||
/// 使用代码编辑器修改此方法的内容。 | |||
/// Required method for Designer support - do not modify | |||
/// the contents of this method with the code editor. | |||
/// </summary> | |||
private void InitializeComponent() | |||
{ | |||
@@ -39,6 +39,7 @@ | |||
this.PasswordTextBox = new System.Windows.Forms.TextBox(); | |||
this.EncryptionLabel = new System.Windows.Forms.Label(); | |||
this.EncryptionSelect = new System.Windows.Forms.ComboBox(); | |||
this.OneTimeAuth = new System.Windows.Forms.CheckBox(); | |||
this.panel2 = new System.Windows.Forms.Panel(); | |||
this.OKButton = new System.Windows.Forms.Button(); | |||
this.MyCancelButton = new System.Windows.Forms.Button(); | |||
@@ -81,37 +82,39 @@ | |||
this.tableLayoutPanel1.Controls.Add(this.PasswordTextBox, 1, 2); | |||
this.tableLayoutPanel1.Controls.Add(this.EncryptionLabel, 0, 3); | |||
this.tableLayoutPanel1.Controls.Add(this.EncryptionSelect, 1, 3); | |||
this.tableLayoutPanel1.Controls.Add(this.OneTimeAuth, 1, 6); | |||
this.tableLayoutPanel1.Location = new System.Drawing.Point(8, 21); | |||
this.tableLayoutPanel1.Margin = new System.Windows.Forms.Padding(0); | |||
this.tableLayoutPanel1.Name = "tableLayoutPanel1"; | |||
this.tableLayoutPanel1.Padding = new System.Windows.Forms.Padding(3); | |||
this.tableLayoutPanel1.RowCount = 6; | |||
this.tableLayoutPanel1.RowCount = 7; | |||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); | |||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); | |||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); | |||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); | |||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); | |||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); | |||
this.tableLayoutPanel1.Size = new System.Drawing.Size(238, 137); | |||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); | |||
this.tableLayoutPanel1.Size = new System.Drawing.Size(249, 162); | |||
this.tableLayoutPanel1.TabIndex = 0; | |||
// | |||
// RemarksTextBox | |||
// | |||
this.RemarksTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); | |||
this.RemarksTextBox.Location = new System.Drawing.Point(72, 111); | |||
this.RemarksTextBox.Location = new System.Drawing.Point(83, 113); | |||
this.RemarksTextBox.MaxLength = 32; | |||
this.RemarksTextBox.Name = "RemarksTextBox"; | |||
this.RemarksTextBox.Size = new System.Drawing.Size(160, 20); | |||
this.RemarksTextBox.TabIndex = 10; | |||
this.RemarksTextBox.Size = new System.Drawing.Size(160, 21); | |||
this.RemarksTextBox.TabIndex = 4; | |||
this.RemarksTextBox.WordWrap = false; | |||
// | |||
// RemarksLabel | |||
// | |||
this.RemarksLabel.Anchor = System.Windows.Forms.AnchorStyles.Right; | |||
this.RemarksLabel.AutoSize = true; | |||
this.RemarksLabel.Location = new System.Drawing.Point(17, 114); | |||
this.RemarksLabel.Location = new System.Drawing.Point(30, 117); | |||
this.RemarksLabel.Name = "RemarksLabel"; | |||
this.RemarksLabel.Size = new System.Drawing.Size(49, 13); | |||
this.RemarksLabel.Size = new System.Drawing.Size(47, 12); | |||
this.RemarksLabel.TabIndex = 9; | |||
this.RemarksLabel.Text = "Remarks"; | |||
// | |||
@@ -119,9 +122,9 @@ | |||
// | |||
this.IPLabel.Anchor = System.Windows.Forms.AnchorStyles.Right; | |||
this.IPLabel.AutoSize = true; | |||
this.IPLabel.Location = new System.Drawing.Point(15, 9); | |||
this.IPLabel.Location = new System.Drawing.Point(18, 10); | |||
this.IPLabel.Name = "IPLabel"; | |||
this.IPLabel.Size = new System.Drawing.Size(51, 13); | |||
this.IPLabel.Size = new System.Drawing.Size(59, 12); | |||
this.IPLabel.TabIndex = 0; | |||
this.IPLabel.Text = "Server IP"; | |||
// | |||
@@ -129,9 +132,9 @@ | |||
// | |||
this.ServerPortLabel.Anchor = System.Windows.Forms.AnchorStyles.Right; | |||
this.ServerPortLabel.AutoSize = true; | |||
this.ServerPortLabel.Location = new System.Drawing.Point(6, 35); | |||
this.ServerPortLabel.Location = new System.Drawing.Point(6, 37); | |||
this.ServerPortLabel.Name = "ServerPortLabel"; | |||
this.ServerPortLabel.Size = new System.Drawing.Size(60, 13); | |||
this.ServerPortLabel.Size = new System.Drawing.Size(71, 12); | |||
this.ServerPortLabel.TabIndex = 1; | |||
this.ServerPortLabel.Text = "Server Port"; | |||
// | |||
@@ -139,40 +142,40 @@ | |||
// | |||
this.PasswordLabel.Anchor = System.Windows.Forms.AnchorStyles.Right; | |||
this.PasswordLabel.AutoSize = true; | |||
this.PasswordLabel.Location = new System.Drawing.Point(13, 61); | |||
this.PasswordLabel.Location = new System.Drawing.Point(24, 64); | |||
this.PasswordLabel.Name = "PasswordLabel"; | |||
this.PasswordLabel.Size = new System.Drawing.Size(53, 13); | |||
this.PasswordLabel.Size = new System.Drawing.Size(53, 12); | |||
this.PasswordLabel.TabIndex = 2; | |||
this.PasswordLabel.Text = "Password"; | |||
// | |||
// IPTextBox | |||
// | |||
this.IPTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); | |||
this.IPTextBox.Location = new System.Drawing.Point(72, 6); | |||
this.IPTextBox.Location = new System.Drawing.Point(83, 6); | |||
this.IPTextBox.MaxLength = 512; | |||
this.IPTextBox.Name = "IPTextBox"; | |||
this.IPTextBox.Size = new System.Drawing.Size(160, 20); | |||
this.IPTextBox.Size = new System.Drawing.Size(160, 21); | |||
this.IPTextBox.TabIndex = 0; | |||
this.IPTextBox.WordWrap = false; | |||
// | |||
// ServerPortTextBox | |||
// | |||
this.ServerPortTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); | |||
this.ServerPortTextBox.Location = new System.Drawing.Point(72, 32); | |||
this.ServerPortTextBox.Location = new System.Drawing.Point(83, 33); | |||
this.ServerPortTextBox.MaxLength = 10; | |||
this.ServerPortTextBox.Name = "ServerPortTextBox"; | |||
this.ServerPortTextBox.Size = new System.Drawing.Size(160, 20); | |||
this.ServerPortTextBox.Size = new System.Drawing.Size(160, 21); | |||
this.ServerPortTextBox.TabIndex = 1; | |||
this.ServerPortTextBox.WordWrap = false; | |||
// | |||
// PasswordTextBox | |||
// | |||
this.PasswordTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); | |||
this.PasswordTextBox.Location = new System.Drawing.Point(72, 58); | |||
this.PasswordTextBox.Location = new System.Drawing.Point(83, 60); | |||
this.PasswordTextBox.MaxLength = 256; | |||
this.PasswordTextBox.Name = "PasswordTextBox"; | |||
this.PasswordTextBox.PasswordChar = '*'; | |||
this.PasswordTextBox.Size = new System.Drawing.Size(160, 20); | |||
this.PasswordTextBox.Size = new System.Drawing.Size(160, 21); | |||
this.PasswordTextBox.TabIndex = 2; | |||
this.PasswordTextBox.WordWrap = false; | |||
// | |||
@@ -180,33 +183,43 @@ | |||
// | |||
this.EncryptionLabel.Anchor = System.Windows.Forms.AnchorStyles.Right; | |||
this.EncryptionLabel.AutoSize = true; | |||
this.EncryptionLabel.Location = new System.Drawing.Point(9, 88); | |||
this.EncryptionLabel.Location = new System.Drawing.Point(12, 91); | |||
this.EncryptionLabel.Name = "EncryptionLabel"; | |||
this.EncryptionLabel.Size = new System.Drawing.Size(57, 13); | |||
this.EncryptionLabel.Size = new System.Drawing.Size(65, 12); | |||
this.EncryptionLabel.TabIndex = 8; | |||
this.EncryptionLabel.Text = "Encryption"; | |||
// | |||
// EncryptionSelect | |||
// | |||
this.EncryptionSelect.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | |||
this.EncryptionSelect.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | |||
| System.Windows.Forms.AnchorStyles.Right))); | |||
this.EncryptionSelect.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; | |||
this.EncryptionSelect.FormattingEnabled = true; | |||
this.EncryptionSelect.ImeMode = System.Windows.Forms.ImeMode.NoControl; | |||
this.EncryptionSelect.ItemHeight = 13; | |||
this.EncryptionSelect.ItemHeight = 12; | |||
this.EncryptionSelect.Items.AddRange(new object[] { | |||
"table", | |||
"rc4-md5", | |||
"salsa20", | |||
"chacha20", | |||
"chacha20-ietf", | |||
"aes-256-cfb", | |||
"aes-192-cfb", | |||
"aes-128-cfb", | |||
"rc4"}); | |||
this.EncryptionSelect.Location = new System.Drawing.Point(72, 84); | |||
"aes-128-cfb"}); | |||
this.EncryptionSelect.Location = new System.Drawing.Point(83, 87); | |||
this.EncryptionSelect.Name = "EncryptionSelect"; | |||
this.EncryptionSelect.Size = new System.Drawing.Size(160, 21); | |||
this.EncryptionSelect.Size = new System.Drawing.Size(160, 20); | |||
this.EncryptionSelect.TabIndex = 3; | |||
this.EncryptionSelect.SelectedIndexChanged += new System.EventHandler(this.EncryptionSelect_SelectedIndexChanged); | |||
// | |||
// OneTimeAuth | |||
// | |||
this.OneTimeAuth.AutoSize = true; | |||
this.OneTimeAuth.Location = new System.Drawing.Point(83, 140); | |||
this.OneTimeAuth.Name = "OneTimeAuth"; | |||
this.OneTimeAuth.Size = new System.Drawing.Size(156, 16); | |||
this.OneTimeAuth.TabIndex = 5; | |||
this.OneTimeAuth.Text = "Onetime Authentication"; | |||
this.OneTimeAuth.UseVisualStyleBackColor = true; | |||
// | |||
// panel2 | |||
// | |||
@@ -226,7 +239,7 @@ | |||
this.OKButton.Margin = new System.Windows.Forms.Padding(3, 3, 3, 0); | |||
this.OKButton.Name = "OKButton"; | |||
this.OKButton.Size = new System.Drawing.Size(75, 23); | |||
this.OKButton.TabIndex = 8; | |||
this.OKButton.TabIndex = 12; | |||
this.OKButton.Text = "OK"; | |||
this.OKButton.UseVisualStyleBackColor = true; | |||
this.OKButton.Click += new System.EventHandler(this.OKButton_Click); | |||
@@ -239,7 +252,7 @@ | |||
this.MyCancelButton.Margin = new System.Windows.Forms.Padding(3, 3, 0, 0); | |||
this.MyCancelButton.Name = "MyCancelButton"; | |||
this.MyCancelButton.Size = new System.Drawing.Size(75, 23); | |||
this.MyCancelButton.TabIndex = 9; | |||
this.MyCancelButton.TabIndex = 13; | |||
this.MyCancelButton.Text = "Cancel"; | |||
this.MyCancelButton.UseVisualStyleBackColor = true; | |||
this.MyCancelButton.Click += new System.EventHandler(this.CancelButton_Click); | |||
@@ -251,7 +264,7 @@ | |||
this.DeleteButton.Margin = new System.Windows.Forms.Padding(3, 6, 0, 3); | |||
this.DeleteButton.Name = "DeleteButton"; | |||
this.DeleteButton.Size = new System.Drawing.Size(80, 23); | |||
this.DeleteButton.TabIndex = 7; | |||
this.DeleteButton.TabIndex = 9; | |||
this.DeleteButton.Text = "&Delete"; | |||
this.DeleteButton.UseVisualStyleBackColor = true; | |||
this.DeleteButton.Click += new System.EventHandler(this.DeleteButton_Click); | |||
@@ -263,7 +276,7 @@ | |||
this.AddButton.Margin = new System.Windows.Forms.Padding(0, 6, 3, 3); | |||
this.AddButton.Name = "AddButton"; | |||
this.AddButton.Size = new System.Drawing.Size(80, 23); | |||
this.AddButton.TabIndex = 6; | |||
this.AddButton.TabIndex = 8; | |||
this.AddButton.Text = "&Add"; | |||
this.AddButton.UseVisualStyleBackColor = true; | |||
this.AddButton.Click += new System.EventHandler(this.AddButton_Click); | |||
@@ -276,8 +289,8 @@ | |||
this.ServerGroupBox.Location = new System.Drawing.Point(178, 0); | |||
this.ServerGroupBox.Margin = new System.Windows.Forms.Padding(12, 0, 0, 0); | |||
this.ServerGroupBox.Name = "ServerGroupBox"; | |||
this.ServerGroupBox.Size = new System.Drawing.Size(249, 174); | |||
this.ServerGroupBox.TabIndex = 6; | |||
this.ServerGroupBox.Size = new System.Drawing.Size(260, 200); | |||
this.ServerGroupBox.TabIndex = 0; | |||
this.ServerGroupBox.TabStop = false; | |||
this.ServerGroupBox.Text = "Server"; | |||
// | |||
@@ -290,7 +303,7 @@ | |||
this.ServersListBox.Margin = new System.Windows.Forms.Padding(0); | |||
this.ServersListBox.Name = "ServersListBox"; | |||
this.ServersListBox.Size = new System.Drawing.Size(166, 148); | |||
this.ServersListBox.TabIndex = 5; | |||
this.ServersListBox.TabIndex = 7; | |||
this.ServersListBox.SelectedIndexChanged += new System.EventHandler(this.ServersListBox_SelectedIndexChanged); | |||
// | |||
// tableLayoutPanel2 | |||
@@ -313,7 +326,7 @@ | |||
this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle()); | |||
this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle()); | |||
this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle()); | |||
this.tableLayoutPanel2.Size = new System.Drawing.Size(427, 238); | |||
this.tableLayoutPanel2.Size = new System.Drawing.Size(438, 265); | |||
this.tableLayoutPanel2.TabIndex = 7; | |||
// | |||
// tableLayoutPanel6 | |||
@@ -326,7 +339,7 @@ | |||
this.tableLayoutPanel6.Controls.Add(this.MoveDownButton, 1, 0); | |||
this.tableLayoutPanel6.Controls.Add(this.MoveUpButton, 0, 0); | |||
this.tableLayoutPanel6.Dock = System.Windows.Forms.DockStyle.Top; | |||
this.tableLayoutPanel6.Location = new System.Drawing.Point(0, 211); | |||
this.tableLayoutPanel6.Location = new System.Drawing.Point(0, 233); | |||
this.tableLayoutPanel6.Margin = new System.Windows.Forms.Padding(0); | |||
this.tableLayoutPanel6.Name = "tableLayoutPanel6"; | |||
this.tableLayoutPanel6.RowCount = 1; | |||
@@ -341,7 +354,7 @@ | |||
this.MoveDownButton.Margin = new System.Windows.Forms.Padding(3, 6, 0, 3); | |||
this.MoveDownButton.Name = "MoveDownButton"; | |||
this.MoveDownButton.Size = new System.Drawing.Size(80, 23); | |||
this.MoveDownButton.TabIndex = 7; | |||
this.MoveDownButton.TabIndex = 11; | |||
this.MoveDownButton.Text = "Move D&own"; | |||
this.MoveDownButton.UseVisualStyleBackColor = true; | |||
this.MoveDownButton.Click += new System.EventHandler(this.MoveDownButton_Click); | |||
@@ -353,7 +366,7 @@ | |||
this.MoveUpButton.Margin = new System.Windows.Forms.Padding(0, 6, 3, 3); | |||
this.MoveUpButton.Name = "MoveUpButton"; | |||
this.MoveUpButton.Size = new System.Drawing.Size(80, 23); | |||
this.MoveUpButton.TabIndex = 6; | |||
this.MoveUpButton.TabIndex = 10; | |||
this.MoveUpButton.Text = "Move &Up"; | |||
this.MoveUpButton.UseVisualStyleBackColor = true; | |||
this.MoveUpButton.Click += new System.EventHandler(this.MoveUpButton_Click); | |||
@@ -369,36 +382,36 @@ | |||
this.tableLayoutPanel5.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); | |||
this.tableLayoutPanel5.Controls.Add(this.ProxyPortTextBox, 1, 0); | |||
this.tableLayoutPanel5.Controls.Add(this.ProxyPortLabel, 0, 0); | |||
this.tableLayoutPanel5.Location = new System.Drawing.Point(241, 174); | |||
this.tableLayoutPanel5.Location = new System.Drawing.Point(242, 200); | |||
this.tableLayoutPanel5.Margin = new System.Windows.Forms.Padding(0); | |||
this.tableLayoutPanel5.Name = "tableLayoutPanel5"; | |||
this.tableLayoutPanel5.Padding = new System.Windows.Forms.Padding(3); | |||
this.tableLayoutPanel5.RowCount = 1; | |||
this.tableLayoutPanel5.RowStyles.Add(new System.Windows.Forms.RowStyle()); | |||
this.tableLayoutPanel5.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 26F)); | |||
this.tableLayoutPanel5.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 26F)); | |||
this.tableLayoutPanel5.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 26F)); | |||
this.tableLayoutPanel5.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 26F)); | |||
this.tableLayoutPanel5.Size = new System.Drawing.Size(186, 32); | |||
this.tableLayoutPanel5.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 27F)); | |||
this.tableLayoutPanel5.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 27F)); | |||
this.tableLayoutPanel5.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 27F)); | |||
this.tableLayoutPanel5.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 27F)); | |||
this.tableLayoutPanel5.Size = new System.Drawing.Size(196, 33); | |||
this.tableLayoutPanel5.TabIndex = 9; | |||
// | |||
// ProxyPortTextBox | |||
// | |||
this.ProxyPortTextBox.Anchor = System.Windows.Forms.AnchorStyles.Left; | |||
this.ProxyPortTextBox.Location = new System.Drawing.Point(67, 6); | |||
this.ProxyPortTextBox.Location = new System.Drawing.Point(77, 6); | |||
this.ProxyPortTextBox.MaxLength = 10; | |||
this.ProxyPortTextBox.Name = "ProxyPortTextBox"; | |||
this.ProxyPortTextBox.Size = new System.Drawing.Size(113, 20); | |||
this.ProxyPortTextBox.TabIndex = 4; | |||
this.ProxyPortTextBox.Size = new System.Drawing.Size(113, 21); | |||
this.ProxyPortTextBox.TabIndex = 6; | |||
this.ProxyPortTextBox.WordWrap = false; | |||
// | |||
// ProxyPortLabel | |||
// | |||
this.ProxyPortLabel.Anchor = System.Windows.Forms.AnchorStyles.Right; | |||
this.ProxyPortLabel.AutoSize = true; | |||
this.ProxyPortLabel.Location = new System.Drawing.Point(6, 9); | |||
this.ProxyPortLabel.Location = new System.Drawing.Point(6, 10); | |||
this.ProxyPortLabel.Name = "ProxyPortLabel"; | |||
this.ProxyPortLabel.Size = new System.Drawing.Size(55, 13); | |||
this.ProxyPortLabel.Size = new System.Drawing.Size(65, 12); | |||
this.ProxyPortLabel.TabIndex = 3; | |||
this.ProxyPortLabel.Text = "Proxy Port"; | |||
// | |||
@@ -413,7 +426,7 @@ | |||
this.tableLayoutPanel3.Controls.Add(this.MyCancelButton, 1, 0); | |||
this.tableLayoutPanel3.Controls.Add(this.OKButton, 0, 0); | |||
this.tableLayoutPanel3.Dock = System.Windows.Forms.DockStyle.Right; | |||
this.tableLayoutPanel3.Location = new System.Drawing.Point(268, 209); | |||
this.tableLayoutPanel3.Location = new System.Drawing.Point(279, 236); | |||
this.tableLayoutPanel3.Margin = new System.Windows.Forms.Padding(3, 3, 0, 3); | |||
this.tableLayoutPanel3.Name = "tableLayoutPanel3"; | |||
this.tableLayoutPanel3.RowCount = 1; | |||
@@ -431,7 +444,7 @@ | |||
this.tableLayoutPanel4.Controls.Add(this.DeleteButton, 1, 0); | |||
this.tableLayoutPanel4.Controls.Add(this.AddButton, 0, 0); | |||
this.tableLayoutPanel4.Dock = System.Windows.Forms.DockStyle.Top; | |||
this.tableLayoutPanel4.Location = new System.Drawing.Point(0, 174); | |||
this.tableLayoutPanel4.Location = new System.Drawing.Point(0, 200); | |||
this.tableLayoutPanel4.Margin = new System.Windows.Forms.Padding(0); | |||
this.tableLayoutPanel4.Name = "tableLayoutPanel4"; | |||
this.tableLayoutPanel4.RowCount = 1; | |||
@@ -505,6 +518,7 @@ | |||
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel6; | |||
private System.Windows.Forms.Button MoveDownButton; | |||
private System.Windows.Forms.Button MoveUpButton; | |||
private System.Windows.Forms.CheckBox OneTimeAuth; | |||
} | |||
} | |||
@@ -48,6 +48,7 @@ namespace Shadowsocks.View | |||
EncryptionLabel.Text = I18N.GetString("Encryption"); | |||
ProxyPortLabel.Text = I18N.GetString("Proxy Port"); | |||
RemarksLabel.Text = I18N.GetString("Remarks"); | |||
OneTimeAuth.Text = I18N.GetString("Onetime Authentication (Experimental)"); | |||
ServerGroupBox.Text = I18N.GetString("Server"); | |||
OKButton.Text = I18N.GetString("OK"); | |||
MyCancelButton.Text = I18N.GetString("Cancel"); | |||
@@ -78,11 +79,12 @@ namespace Shadowsocks.View | |||
} | |||
Server server = new Server | |||
{ | |||
server = IPTextBox.Text, | |||
server = IPTextBox.Text.Trim(), | |||
server_port = int.Parse(ServerPortTextBox.Text), | |||
password = PasswordTextBox.Text, | |||
method = EncryptionSelect.Text, | |||
remarks = RemarksTextBox.Text | |||
remarks = RemarksTextBox.Text, | |||
auth = OneTimeAuth.Checked | |||
}; | |||
int localPort = int.Parse(ProxyPortTextBox.Text); | |||
Configuration.CheckServer(server); | |||
@@ -115,6 +117,7 @@ namespace Shadowsocks.View | |||
ProxyPortTextBox.Text = _modifiedConfiguration.localPort.ToString(); | |||
EncryptionSelect.Text = server.method ?? "aes-256-cfb"; | |||
RemarksTextBox.Text = server.remarks; | |||
OneTimeAuth.Checked = server.auth; | |||
} | |||
} | |||
@@ -146,6 +149,28 @@ namespace Shadowsocks.View | |||
} | |||
private void ConfigForm_KeyDown(object sender, KeyEventArgs e) | |||
{ | |||
// Sometimes the users may hit enter key by mistake, and the form will close without saving entries. | |||
if (e.KeyCode == Keys.Enter) | |||
{ | |||
Server server = controller.GetCurrentServer(); | |||
if (!SaveOldSelectedServer()) | |||
{ | |||
return; | |||
} | |||
if (_modifiedConfiguration.configs.Count == 0) | |||
{ | |||
MessageBox.Show(I18N.GetString("Please add at least one server")); | |||
return; | |||
} | |||
controller.SaveServers(_modifiedConfiguration.configs, _modifiedConfiguration.localPort); | |||
controller.SelectServerIndex(_modifiedConfiguration.configs.IndexOf(server)); | |||
} | |||
} | |||
private void ServersListBox_SelectedIndexChanged(object sender, EventArgs e) | |||
{ | |||
if (!ServersListBox.CanSelect) | |||
@@ -163,7 +188,10 @@ namespace Shadowsocks.View | |||
ServersListBox.SelectedIndex = _lastSelectedIndex; | |||
return; | |||
} | |||
ServersListBox.Items[_lastSelectedIndex] = _modifiedConfiguration.configs[_lastSelectedIndex].FriendlyName(); | |||
if (_lastSelectedIndex >= 0) | |||
{ | |||
ServersListBox.Items[_lastSelectedIndex] = _modifiedConfiguration.configs[_lastSelectedIndex].FriendlyName(); | |||
} | |||
UpdateMoveUpAndDownButton(); | |||
LoadSelectedServer(); | |||
_lastSelectedIndex = ServersListBox.SelectedIndex; | |||
@@ -297,5 +325,18 @@ namespace Shadowsocks.View | |||
MoveConfigItem(+1); // +1 means move forward | |||
} | |||
} | |||
private void EncryptionSelect_SelectedIndexChanged(object sender, EventArgs e) | |||
{ | |||
if (EncryptionSelect.Text == "rc4" || EncryptionSelect.Text == "table") | |||
{ | |||
OneTimeAuth.Enabled = false; | |||
OneTimeAuth.Checked = false; | |||
} | |||
else | |||
{ | |||
OneTimeAuth.Enabled = true; | |||
} | |||
} | |||
} | |||
} |
@@ -30,19 +30,25 @@ | |||
{ | |||
this.components = new System.ComponentModel.Container(); | |||
this.LogMessageTextBox = new System.Windows.Forms.TextBox(); | |||
this.contextMenuStrip1 = new System.Windows.Forms.ContextMenuStrip(this.components); | |||
this.mainMenu1 = new System.Windows.Forms.MainMenu(this.components); | |||
this.MainMenu = new System.Windows.Forms.MainMenu(this.components); | |||
this.FileMenuItem = new System.Windows.Forms.MenuItem(); | |||
this.OpenLocationMenuItem = new System.Windows.Forms.MenuItem(); | |||
this.ExitMenuItem = new System.Windows.Forms.MenuItem(); | |||
this.panel1 = new System.Windows.Forms.Panel(); | |||
this.ViewMenuItem = new System.Windows.Forms.MenuItem(); | |||
this.CleanLogsMenuItem = new System.Windows.Forms.MenuItem(); | |||
this.ChangeFontMenuItem = new System.Windows.Forms.MenuItem(); | |||
this.WrapTextMenuItem = new System.Windows.Forms.MenuItem(); | |||
this.TopMostMenuItem = new System.Windows.Forms.MenuItem(); | |||
this.MenuItemSeparater = new System.Windows.Forms.MenuItem(); | |||
this.ShowToolbarMenuItem = new System.Windows.Forms.MenuItem(); | |||
this.TopMostCheckBox = new System.Windows.Forms.CheckBox(); | |||
this.ChangeFontButton = new System.Windows.Forms.Button(); | |||
this.CleanLogsButton = new System.Windows.Forms.Button(); | |||
this.WrapTextCheckBox = new System.Windows.Forms.CheckBox(); | |||
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); | |||
this.TopMostCheckBox = new System.Windows.Forms.CheckBox(); | |||
this.panel1.SuspendLayout(); | |||
this.ToolbarFlowLayoutPanel = new System.Windows.Forms.FlowLayoutPanel(); | |||
this.tableLayoutPanel1.SuspendLayout(); | |||
this.ToolbarFlowLayoutPanel.SuspendLayout(); | |||
this.SuspendLayout(); | |||
// | |||
// LogMessageTextBox | |||
@@ -51,25 +57,20 @@ | |||
this.LogMessageTextBox.Dock = System.Windows.Forms.DockStyle.Fill; | |||
this.LogMessageTextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); | |||
this.LogMessageTextBox.ForeColor = System.Drawing.Color.White; | |||
this.LogMessageTextBox.Location = new System.Drawing.Point(3, 43); | |||
this.LogMessageTextBox.Location = new System.Drawing.Point(3, 40); | |||
this.LogMessageTextBox.MaxLength = 2147483647; | |||
this.LogMessageTextBox.Multiline = true; | |||
this.LogMessageTextBox.Name = "LogMessageTextBox"; | |||
this.LogMessageTextBox.ReadOnly = true; | |||
this.LogMessageTextBox.ScrollBars = System.Windows.Forms.ScrollBars.Both; | |||
this.LogMessageTextBox.Size = new System.Drawing.Size(541, 307); | |||
this.LogMessageTextBox.Size = new System.Drawing.Size(378, 131); | |||
this.LogMessageTextBox.TabIndex = 0; | |||
this.LogMessageTextBox.WordWrap = false; | |||
// | |||
// contextMenuStrip1 | |||
// | |||
this.contextMenuStrip1.Name = "contextMenuStrip1"; | |||
this.contextMenuStrip1.Size = new System.Drawing.Size(61, 4); | |||
// | |||
// mainMenu1 | |||
// MainMenu | |||
// | |||
this.mainMenu1.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] { | |||
this.FileMenuItem}); | |||
this.MainMenu.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] { | |||
this.FileMenuItem, | |||
this.ViewMenuItem}); | |||
// | |||
// FileMenuItem | |||
// | |||
@@ -91,23 +92,72 @@ | |||
this.ExitMenuItem.Text = "E&xit"; | |||
this.ExitMenuItem.Click += new System.EventHandler(this.ExitMenuItem_Click); | |||
// | |||
// panel1 | |||
// ViewMenuItem | |||
// | |||
this.ViewMenuItem.Index = 1; | |||
this.ViewMenuItem.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] { | |||
this.CleanLogsMenuItem, | |||
this.ChangeFontMenuItem, | |||
this.WrapTextMenuItem, | |||
this.TopMostMenuItem, | |||
this.MenuItemSeparater, | |||
this.ShowToolbarMenuItem}); | |||
this.ViewMenuItem.Text = "&View"; | |||
// | |||
// CleanLogsMenuItem | |||
// | |||
this.CleanLogsMenuItem.Index = 0; | |||
this.CleanLogsMenuItem.Text = "&Clean Logs"; | |||
this.CleanLogsMenuItem.Click += new System.EventHandler(this.CleanLogsMenuItem_Click); | |||
// | |||
// ChangeFontMenuItem | |||
// | |||
this.ChangeFontMenuItem.Index = 1; | |||
this.ChangeFontMenuItem.Text = "Change &Font"; | |||
this.ChangeFontMenuItem.Click += new System.EventHandler(this.ChangeFontMenuItem_Click); | |||
// | |||
// WrapTextMenuItem | |||
// | |||
this.WrapTextMenuItem.Index = 2; | |||
this.WrapTextMenuItem.Text = "&Wrap Text"; | |||
this.WrapTextMenuItem.Click += new System.EventHandler(this.WrapTextMenuItem_Click); | |||
// | |||
// TopMostMenuItem | |||
// | |||
this.TopMostMenuItem.Index = 3; | |||
this.TopMostMenuItem.Text = "&Top Most"; | |||
this.TopMostMenuItem.Click += new System.EventHandler(this.TopMostMenuItem_Click); | |||
// | |||
// MenuItemSeparater | |||
// | |||
this.MenuItemSeparater.Index = 4; | |||
this.MenuItemSeparater.Text = "-"; | |||
// | |||
// ShowToolbarMenuItem | |||
// | |||
this.panel1.Controls.Add(this.TopMostCheckBox); | |||
this.panel1.Controls.Add(this.ChangeFontButton); | |||
this.panel1.Controls.Add(this.CleanLogsButton); | |||
this.panel1.Controls.Add(this.WrapTextCheckBox); | |||
this.panel1.Dock = System.Windows.Forms.DockStyle.Fill; | |||
this.panel1.Location = new System.Drawing.Point(3, 3); | |||
this.panel1.Name = "panel1"; | |||
this.panel1.Size = new System.Drawing.Size(541, 34); | |||
this.panel1.TabIndex = 1; | |||
this.ShowToolbarMenuItem.Index = 5; | |||
this.ShowToolbarMenuItem.Text = "&Show Toolbar"; | |||
this.ShowToolbarMenuItem.Click += new System.EventHandler(this.ShowToolbarMenuItem_Click); | |||
// | |||
// TopMostCheckBox | |||
// | |||
this.TopMostCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | |||
| System.Windows.Forms.AnchorStyles.Left))); | |||
this.TopMostCheckBox.AutoSize = true; | |||
this.TopMostCheckBox.Location = new System.Drawing.Point(247, 3); | |||
this.TopMostCheckBox.Name = "TopMostCheckBox"; | |||
this.TopMostCheckBox.Size = new System.Drawing.Size(71, 25); | |||
this.TopMostCheckBox.TabIndex = 3; | |||
this.TopMostCheckBox.Text = "&Top Most"; | |||
this.TopMostCheckBox.UseVisualStyleBackColor = true; | |||
this.TopMostCheckBox.CheckedChanged += new System.EventHandler(this.TopMostCheckBox_CheckedChanged); | |||
// | |||
// ChangeFontButton | |||
// | |||
this.ChangeFontButton.Location = new System.Drawing.Point(107, 4); | |||
this.ChangeFontButton.AutoSize = true; | |||
this.ChangeFontButton.Location = new System.Drawing.Point(84, 3); | |||
this.ChangeFontButton.Name = "ChangeFontButton"; | |||
this.ChangeFontButton.Size = new System.Drawing.Size(75, 23); | |||
this.ChangeFontButton.Size = new System.Drawing.Size(75, 25); | |||
this.ChangeFontButton.TabIndex = 2; | |||
this.ChangeFontButton.Text = "&Font"; | |||
this.ChangeFontButton.UseVisualStyleBackColor = true; | |||
@@ -115,22 +165,25 @@ | |||
// | |||
// CleanLogsButton | |||
// | |||
this.CleanLogsButton.Location = new System.Drawing.Point(9, 4); | |||
this.CleanLogsButton.AutoSize = true; | |||
this.CleanLogsButton.Location = new System.Drawing.Point(3, 3); | |||
this.CleanLogsButton.Name = "CleanLogsButton"; | |||
this.CleanLogsButton.Size = new System.Drawing.Size(75, 23); | |||
this.CleanLogsButton.Size = new System.Drawing.Size(75, 25); | |||
this.CleanLogsButton.TabIndex = 1; | |||
this.CleanLogsButton.Text = "&Clean logs"; | |||
this.CleanLogsButton.Text = "&Clean Logs"; | |||
this.CleanLogsButton.UseVisualStyleBackColor = true; | |||
this.CleanLogsButton.Click += new System.EventHandler(this.CleanLogsButton_Click); | |||
// | |||
// WrapTextCheckBox | |||
// | |||
this.WrapTextCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | |||
| System.Windows.Forms.AnchorStyles.Left))); | |||
this.WrapTextCheckBox.AutoSize = true; | |||
this.WrapTextCheckBox.Location = new System.Drawing.Point(209, 9); | |||
this.WrapTextCheckBox.Location = new System.Drawing.Point(165, 3); | |||
this.WrapTextCheckBox.Name = "WrapTextCheckBox"; | |||
this.WrapTextCheckBox.Size = new System.Drawing.Size(78, 16); | |||
this.WrapTextCheckBox.Size = new System.Drawing.Size(76, 25); | |||
this.WrapTextCheckBox.TabIndex = 0; | |||
this.WrapTextCheckBox.Text = "&Wrap text"; | |||
this.WrapTextCheckBox.Text = "&Wrap Text"; | |||
this.WrapTextCheckBox.UseVisualStyleBackColor = true; | |||
this.WrapTextCheckBox.CheckedChanged += new System.EventHandler(this.WrapTextCheckBox_CheckedChanged); | |||
// | |||
@@ -138,45 +191,48 @@ | |||
// | |||
this.tableLayoutPanel1.ColumnCount = 1; | |||
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); | |||
this.tableLayoutPanel1.Controls.Add(this.panel1, 0, 0); | |||
this.tableLayoutPanel1.Controls.Add(this.LogMessageTextBox, 0, 1); | |||
this.tableLayoutPanel1.Controls.Add(this.ToolbarFlowLayoutPanel, 0, 0); | |||
this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill; | |||
this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0); | |||
this.tableLayoutPanel1.Name = "tableLayoutPanel1"; | |||
this.tableLayoutPanel1.RowCount = 2; | |||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 40F)); | |||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); | |||
this.tableLayoutPanel1.Size = new System.Drawing.Size(547, 353); | |||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); | |||
this.tableLayoutPanel1.Size = new System.Drawing.Size(384, 174); | |||
this.tableLayoutPanel1.TabIndex = 2; | |||
// | |||
// TopMostCheckBox | |||
// ToolbarFlowLayoutPanel | |||
// | |||
this.TopMostCheckBox.AutoSize = true; | |||
this.TopMostCheckBox.Location = new System.Drawing.Point(311, 9); | |||
this.TopMostCheckBox.Name = "TopMostCheckBox"; | |||
this.TopMostCheckBox.Size = new System.Drawing.Size(72, 16); | |||
this.TopMostCheckBox.TabIndex = 3; | |||
this.TopMostCheckBox.Text = "&Top most"; | |||
this.TopMostCheckBox.UseVisualStyleBackColor = true; | |||
this.TopMostCheckBox.CheckedChanged += new System.EventHandler(this.TopMostCheckBox_CheckedChanged); | |||
this.ToolbarFlowLayoutPanel.AutoSize = true; | |||
this.ToolbarFlowLayoutPanel.Controls.Add(this.CleanLogsButton); | |||
this.ToolbarFlowLayoutPanel.Controls.Add(this.ChangeFontButton); | |||
this.ToolbarFlowLayoutPanel.Controls.Add(this.WrapTextCheckBox); | |||
this.ToolbarFlowLayoutPanel.Controls.Add(this.TopMostCheckBox); | |||
this.ToolbarFlowLayoutPanel.Dock = System.Windows.Forms.DockStyle.Fill; | |||
this.ToolbarFlowLayoutPanel.Location = new System.Drawing.Point(3, 3); | |||
this.ToolbarFlowLayoutPanel.Name = "ToolbarFlowLayoutPanel"; | |||
this.ToolbarFlowLayoutPanel.Size = new System.Drawing.Size(378, 31); | |||
this.ToolbarFlowLayoutPanel.TabIndex = 2; | |||
// | |||
// LogForm | |||
// | |||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); | |||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); | |||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; | |||
this.ClientSize = new System.Drawing.Size(547, 353); | |||
this.ClientSize = new System.Drawing.Size(384, 174); | |||
this.Controls.Add(this.tableLayoutPanel1); | |||
this.Menu = this.mainMenu1; | |||
this.Menu = this.MainMenu; | |||
this.MinimumSize = new System.Drawing.Size(400, 213); | |||
this.Name = "LogForm"; | |||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; | |||
this.Text = "Log Viewer"; | |||
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.LogForm_FormClosing); | |||
this.Load += new System.EventHandler(this.LogForm_Load); | |||
this.Shown += new System.EventHandler(this.LogForm_Shown); | |||
this.panel1.ResumeLayout(false); | |||
this.panel1.PerformLayout(); | |||
this.tableLayoutPanel1.ResumeLayout(false); | |||
this.tableLayoutPanel1.PerformLayout(); | |||
this.ToolbarFlowLayoutPanel.ResumeLayout(false); | |||
this.ToolbarFlowLayoutPanel.PerformLayout(); | |||
this.ResumeLayout(false); | |||
} | |||
@@ -184,16 +240,22 @@ | |||
#endregion | |||
private System.Windows.Forms.TextBox LogMessageTextBox; | |||
private System.Windows.Forms.ContextMenuStrip contextMenuStrip1; | |||
private System.Windows.Forms.MainMenu mainMenu1; | |||
private System.Windows.Forms.MainMenu MainMenu; | |||
private System.Windows.Forms.MenuItem FileMenuItem; | |||
private System.Windows.Forms.MenuItem OpenLocationMenuItem; | |||
private System.Windows.Forms.MenuItem ExitMenuItem; | |||
private System.Windows.Forms.Panel panel1; | |||
private System.Windows.Forms.CheckBox WrapTextCheckBox; | |||
private System.Windows.Forms.Button CleanLogsButton; | |||
private System.Windows.Forms.Button ChangeFontButton; | |||
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; | |||
private System.Windows.Forms.CheckBox TopMostCheckBox; | |||
private System.Windows.Forms.MenuItem ViewMenuItem; | |||
private System.Windows.Forms.MenuItem CleanLogsMenuItem; | |||
private System.Windows.Forms.MenuItem ChangeFontMenuItem; | |||
private System.Windows.Forms.MenuItem WrapTextMenuItem; | |||
private System.Windows.Forms.MenuItem TopMostMenuItem; | |||
private System.Windows.Forms.FlowLayoutPanel ToolbarFlowLayoutPanel; | |||
private System.Windows.Forms.MenuItem MenuItemSeparater; | |||
private System.Windows.Forms.MenuItem ShowToolbarMenuItem; | |||
} | |||
} |
@@ -1,15 +1,13 @@ | |||
using Shadowsocks.Controller; | |||
using Shadowsocks.Properties; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.ComponentModel; | |||
using System.Data; | |||
using System; | |||
using System.Drawing; | |||
using System.IO; | |||
using System.Linq; | |||
using System.Text; | |||
using System.Windows.Forms; | |||
using Shadowsocks.Controller; | |||
using Shadowsocks.Properties; | |||
using Shadowsocks.Model; | |||
using Shadowsocks.Util; | |||
namespace Shadowsocks.View | |||
{ | |||
public partial class LogForm : Form | |||
@@ -18,12 +16,28 @@ namespace Shadowsocks.View | |||
string filename; | |||
Timer timer; | |||
const int BACK_OFFSET = 65536; | |||
ShadowsocksController controller; | |||
public LogForm(string filename) | |||
public LogForm(ShadowsocksController controller, string filename) | |||
{ | |||
this.controller = controller; | |||
this.filename = filename; | |||
InitializeComponent(); | |||
this.Icon = Icon.FromHandle(Resources.ssw128.GetHicon()); | |||
Icon = Icon.FromHandle(Resources.ssw128.GetHicon()); | |||
LogViewerConfig config = controller.GetConfigurationCopy().logViewer; | |||
if (config == null) | |||
{ | |||
config = new LogViewerConfig(); | |||
} | |||
else { | |||
topMostTrigger = config.topMost; | |||
wrapTextTrigger = config.wrapText; | |||
toolbarTrigger = config.toolbarShown; | |||
LogMessageTextBox.BackColor = config.GetBackgroundColor(); | |||
LogMessageTextBox.ForeColor = config.GetTextColor(); | |||
LogMessageTextBox.Font = config.GetFont(); | |||
} | |||
UpdateTexts(); | |||
} | |||
@@ -33,11 +47,17 @@ namespace Shadowsocks.View | |||
FileMenuItem.Text = I18N.GetString("&File"); | |||
OpenLocationMenuItem.Text = I18N.GetString("&Open Location"); | |||
ExitMenuItem.Text = I18N.GetString("E&xit"); | |||
CleanLogsButton.Text = I18N.GetString("&Clean logs"); | |||
ChangeFontButton.Text = I18N.GetString("&Font"); | |||
WrapTextCheckBox.Text = I18N.GetString("&Wrap text"); | |||
TopMostCheckBox.Text = I18N.GetString("&Top most"); | |||
this.Text = I18N.GetString("Log Viewer"); | |||
CleanLogsButton.Text = I18N.GetString("&Clean Logs"); | |||
ChangeFontButton.Text = I18N.GetString("Change &Font"); | |||
WrapTextCheckBox.Text = I18N.GetString("&Wrap Text"); | |||
TopMostCheckBox.Text = I18N.GetString("&Top Most"); | |||
ViewMenuItem.Text = I18N.GetString("&View"); | |||
CleanLogsMenuItem.Text = I18N.GetString("&Clean Logs"); | |||
ChangeFontMenuItem.Text = I18N.GetString("Change &Font"); | |||
WrapTextMenuItem.Text = I18N.GetString("&Wrap Text"); | |||
TopMostMenuItem.Text = I18N.GetString("&Top Most"); | |||
ShowToolbarMenuItem.Text = I18N.GetString("&Show Toolbar"); | |||
Text = I18N.GetString("Log Viewer"); | |||
} | |||
private void Timer_Tick(object sender, EventArgs e) | |||
@@ -58,7 +78,7 @@ namespace Shadowsocks.View | |||
string line = ""; | |||
while ((line = reader.ReadLine()) != null) | |||
LogMessageTextBox.AppendText(line + "\r\n"); | |||
LogMessageTextBox.AppendText(line + Environment.NewLine); | |||
LogMessageTextBox.ScrollToCaret(); | |||
@@ -78,7 +98,7 @@ namespace Shadowsocks.View | |||
while ((line = reader.ReadLine()) != null) | |||
{ | |||
changed = true; | |||
LogMessageTextBox.AppendText(line + "\r\n"); | |||
LogMessageTextBox.AppendText(line + Environment.NewLine); | |||
} | |||
if (changed) | |||
@@ -88,32 +108,68 @@ namespace Shadowsocks.View | |||
lastOffset = reader.BaseStream.Position; | |||
} | |||
this.Text = I18N.GetString("Log Viewer") + | |||
$" [in: {Utils.FormatBandwidth(controller.inboundCounter)}, out: {Utils.FormatBandwidth(controller.outboundCounter)}]"; | |||
} | |||
private void LogForm_Load(object sender, EventArgs e) | |||
{ | |||
InitContent(); | |||
timer = new Timer(); | |||
timer.Interval = 300; | |||
timer.Tick += Timer_Tick; | |||
timer.Start(); | |||
LogViewerConfig config = controller.GetConfigurationCopy().logViewer; | |||
if (config == null) | |||
config = new LogViewerConfig(); | |||
Height = config.height; | |||
Width = config.width; | |||
Top = config.GetBestTop(); | |||
Left = config.GetBestLeft(); | |||
topMostTriggerLock = true; | |||
TopMost = TopMostMenuItem.Checked = TopMostCheckBox.Checked = topMostTrigger; | |||
topMostTriggerLock = false; | |||
wrapTextTriggerLock = true; | |||
LogMessageTextBox.WordWrap = WrapTextMenuItem.Checked = WrapTextCheckBox.Checked = wrapTextTrigger; | |||
wrapTextTriggerLock = false; | |||
ToolbarFlowLayoutPanel.Visible = ShowToolbarMenuItem.Checked = toolbarTrigger; | |||
} | |||
private void LogForm_FormClosing(object sender, FormClosingEventArgs e) | |||
{ | |||
timer.Stop(); | |||
LogViewerConfig config = controller.GetConfigurationCopy().logViewer; | |||
if (config == null) | |||
config = new LogViewerConfig(); | |||
config.topMost = topMostTrigger; | |||
config.wrapText = wrapTextTrigger; | |||
config.toolbarShown = toolbarTrigger; | |||
config.SetFont(LogMessageTextBox.Font); | |||
config.SetBackgroundColor(LogMessageTextBox.BackColor); | |||
config.SetTextColor(LogMessageTextBox.ForeColor); | |||
config.top = Top; | |||
config.left = Left; | |||
config.height = Height; | |||
config.width = Width; | |||
controller.SaveLogViewerConfig(config); | |||
} | |||
private void OpenLocationMenuItem_Click(object sender, EventArgs e) | |||
{ | |||
string argument = "/select, \"" + filename + "\""; | |||
Console.WriteLine(argument); | |||
Logging.Debug(argument); | |||
System.Diagnostics.Process.Start("explorer.exe", argument); | |||
} | |||
private void ExitMenuItem_Click(object sender, EventArgs e) | |||
{ | |||
this.Close(); | |||
Close(); | |||
} | |||
private void LogForm_Shown(object sender, EventArgs e) | |||
@@ -121,30 +177,125 @@ namespace Shadowsocks.View | |||
LogMessageTextBox.ScrollToCaret(); | |||
} | |||
private void WrapTextCheckBox_CheckedChanged(object sender, EventArgs e) | |||
#region Clean up the content in LogMessageTextBox. | |||
private void DoCleanLogs() | |||
{ | |||
LogMessageTextBox.WordWrap = WrapTextCheckBox.Checked; | |||
LogMessageTextBox.ScrollToCaret(); | |||
LogMessageTextBox.Clear(); | |||
} | |||
private void CleanLogsMenuItem_Click(object sender, EventArgs e) | |||
{ | |||
DoCleanLogs(); | |||
} | |||
private void CleanLogsButton_Click(object sender, EventArgs e) | |||
{ | |||
LogMessageTextBox.Clear(); | |||
DoCleanLogs(); | |||
} | |||
#endregion | |||
#region Change the font settings applied in LogMessageTextBox. | |||
private void DoChangeFont() | |||
{ | |||
try | |||
{ | |||
FontDialog fd = new FontDialog(); | |||
fd.Font = LogMessageTextBox.Font; | |||
if (fd.ShowDialog() == DialogResult.OK) | |||
{ | |||
LogMessageTextBox.Font = new Font(fd.Font.FontFamily, fd.Font.Size, fd.Font.Style); | |||
} | |||
} | |||
catch (Exception ex) | |||
{ | |||
Logging.LogUsefulException(ex); | |||
MessageBox.Show(ex.Message); | |||
} | |||
} | |||
private void ChangeFontMenuItem_Click(object sender, EventArgs e) | |||
{ | |||
DoChangeFont(); | |||
} | |||
private void ChangeFontButton_Click(object sender, EventArgs e) | |||
{ | |||
FontDialog fd = new FontDialog(); | |||
fd.Font = LogMessageTextBox.Font; | |||
if (fd.ShowDialog() == DialogResult.OK) | |||
DoChangeFont(); | |||
} | |||
#endregion | |||
#region Trigger the log messages to wrapable, or not. | |||
bool wrapTextTrigger = false; | |||
bool wrapTextTriggerLock = false; | |||
private void TriggerWrapText() | |||
{ | |||
wrapTextTriggerLock = true; | |||
wrapTextTrigger = !wrapTextTrigger; | |||
LogMessageTextBox.WordWrap = wrapTextTrigger; | |||
LogMessageTextBox.ScrollToCaret(); | |||
WrapTextMenuItem.Checked = WrapTextCheckBox.Checked = wrapTextTrigger; | |||
wrapTextTriggerLock = false; | |||
} | |||
private void WrapTextMenuItem_Click(object sender, EventArgs e) | |||
{ | |||
if (!wrapTextTriggerLock) | |||
{ | |||
TriggerWrapText(); | |||
} | |||
} | |||
private void WrapTextCheckBox_CheckedChanged(object sender, EventArgs e) | |||
{ | |||
if (!wrapTextTriggerLock) | |||
{ | |||
LogMessageTextBox.Font = fd.Font; | |||
TriggerWrapText(); | |||
} | |||
} | |||
#endregion | |||
#region Trigger the window to top most, or not. | |||
bool topMostTrigger = false; | |||
bool topMostTriggerLock = false; | |||
private void TriggerTopMost() | |||
{ | |||
topMostTriggerLock = true; | |||
topMostTrigger = !topMostTrigger; | |||
TopMost = topMostTrigger; | |||
TopMostMenuItem.Checked = TopMostCheckBox.Checked = topMostTrigger; | |||
topMostTriggerLock = false; | |||
} | |||
private void TopMostCheckBox_CheckedChanged(object sender, EventArgs e) | |||
{ | |||
this.TopMost = TopMostCheckBox.Checked; | |||
if (!topMostTriggerLock) | |||
{ | |||
TriggerTopMost(); | |||
} | |||
} | |||
private void TopMostMenuItem_Click(object sender, EventArgs e) | |||
{ | |||
if (!topMostTriggerLock) | |||
{ | |||
TriggerTopMost(); | |||
} | |||
} | |||
#endregion | |||
private bool toolbarTrigger = false; | |||
private void ShowToolbarMenuItem_Click(object sender, EventArgs e) | |||
{ | |||
toolbarTrigger = !toolbarTrigger; | |||
ToolbarFlowLayoutPanel.Visible = toolbarTrigger; | |||
ShowToolbarMenuItem.Checked = toolbarTrigger; | |||
} | |||
} | |||
} |
@@ -117,10 +117,31 @@ | |||
<resheader name="writer"> | |||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||
</resheader> | |||
<metadata name="contextMenuStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> | |||
<metadata name="LogMessageTextBox.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> | |||
<value>True</value> | |||
</metadata> | |||
<metadata name="MainMenu.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> | |||
<value>17, 17</value> | |||
</metadata> | |||
<metadata name="mainMenu1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> | |||
<value>172, 17</value> | |||
<metadata name="TopMostCheckBox.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> | |||
<value>True</value> | |||
</metadata> | |||
<metadata name="ChangeFontButton.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> | |||
<value>True</value> | |||
</metadata> | |||
<metadata name="CleanLogsButton.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> | |||
<value>True</value> | |||
</metadata> | |||
<metadata name="WrapTextCheckBox.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> | |||
<value>True</value> | |||
</metadata> | |||
<metadata name="tableLayoutPanel1.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> | |||
<value>True</value> | |||
</metadata> | |||
<metadata name="ToolbarFlowLayoutPanel.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> | |||
<value>True</value> | |||
</metadata> | |||
<metadata name="$this.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> | |||
<value>True</value> | |||
</metadata> | |||
</root> |
@@ -1,16 +1,18 @@ | |||
using Shadowsocks.Controller; | |||
using Shadowsocks.Model; | |||
using Shadowsocks.Properties; | |||
using System; | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Diagnostics; | |||
using System.Drawing; | |||
using System.Text; | |||
using System.Windows.Forms; | |||
using ZXing; | |||
using ZXing.Common; | |||
using ZXing.QrCode; | |||
using Shadowsocks.Controller; | |||
using Shadowsocks.Model; | |||
using Shadowsocks.Properties; | |||
using Shadowsocks.Util; | |||
namespace Shadowsocks.View | |||
{ | |||
public class MenuViewController | |||
@@ -26,10 +28,10 @@ namespace Shadowsocks.View | |||
private ContextMenu contextMenu1; | |||
private bool _isFirstRun; | |||
private bool _isStartupChecking; | |||
private MenuItem enableItem; | |||
private MenuItem modeItem; | |||
private MenuItem AutoStartupItem; | |||
private MenuItem AvailabilityStatistics; | |||
private MenuItem ShareOverLANItem; | |||
private MenuItem SeperatorItem; | |||
private MenuItem ConfigItem; | |||
@@ -42,7 +44,10 @@ namespace Shadowsocks.View | |||
private MenuItem updateFromGFWListItem; | |||
private MenuItem editGFWUserRuleItem; | |||
private MenuItem editOnlinePACItem; | |||
private MenuItem autoCheckUpdatesToggleItem; | |||
private ConfigForm configForm; | |||
private List<LogForm> logForms = new List<LogForm>(); | |||
private bool logFormsVisible = false; | |||
private string _urlToOpen; | |||
public MenuViewController(ShadowsocksController controller) | |||
@@ -65,16 +70,25 @@ namespace Shadowsocks.View | |||
UpdateTrayIcon(); | |||
_notifyIcon.Visible = true; | |||
_notifyIcon.ContextMenu = contextMenu1; | |||
_notifyIcon.BalloonTipClicked += notifyIcon1_BalloonTipClicked; | |||
_notifyIcon.MouseClick += notifyIcon1_Click; | |||
_notifyIcon.MouseDoubleClick += notifyIcon1_DoubleClick; | |||
_notifyIcon.BalloonTipClosed += _notifyIcon_BalloonTipClosed; | |||
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, 3000); | |||
} | |||
if (config.isDefault) | |||
{ | |||
_isFirstRun = true; | |||
ShowConfigForm(); | |||
@@ -164,6 +178,7 @@ namespace Shadowsocks.View | |||
this.ServersItem = CreateMenuGroup("Servers", new MenuItem[] { | |||
this.SeperatorItem = new MenuItem("-"), | |||
this.ConfigItem = CreateMenuItem("Edit Servers...", new EventHandler(this.Config_Click)), | |||
CreateMenuItem("Statistics Config...", StatisticsConfigItem_Click), | |||
CreateMenuItem("Show QRCode...", new EventHandler(this.QRCodeItem_Click)), | |||
CreateMenuItem("Scan QRCode from Screen...", new EventHandler(this.ScanQRCodeItem_Click)) | |||
}), | |||
@@ -178,10 +193,14 @@ namespace Shadowsocks.View | |||
}), | |||
new MenuItem("-"), | |||
this.AutoStartupItem = CreateMenuItem("Start on Boot", new EventHandler(this.AutoStartupItem_Click)), | |||
this.AvailabilityStatistics = CreateMenuItem("Availability Statistics", new EventHandler(this.AvailabilityStatisticsItem_Click)), | |||
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 for Updates...", new EventHandler(this.checkUpdatesItem_Click)), | |||
new MenuItem("-"), | |||
this.autoCheckUpdatesToggleItem = CreateMenuItem("Check for Updates at Startup", new EventHandler(this.autoCheckUpdatesToggleItem_Click)), | |||
}), | |||
CreateMenuItem("About...", new EventHandler(this.AboutItem_Click)), | |||
new MenuItem("-"), | |||
CreateMenuItem("Quit", new EventHandler(this.Quit_Click)) | |||
@@ -238,19 +257,41 @@ 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); | |||
_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; | |||
if (updateChecker.NewVersionFound) | |||
{ | |||
updateChecker.NewVersionFound = false; /* Reset the flag */ | |||
if (System.IO.File.Exists(updateChecker.LatestVersionLocalName)) | |||
{ | |||
string argument = "/select, \"" + updateChecker.LatestVersionLocalName + "\""; | |||
System.Diagnostics.Process.Start("explorer.exe", argument); | |||
} | |||
} | |||
} | |||
private void _notifyIcon_BalloonTipClosed(object sender, EventArgs e) | |||
{ | |||
if (updateChecker.NewVersionFound) | |||
{ | |||
updateChecker.NewVersionFound = false; /* Reset the flag */ | |||
} | |||
} | |||
private void LoadCurrentConfiguration() | |||
{ | |||
@@ -262,10 +303,10 @@ namespace Shadowsocks.View | |||
PACModeItem.Checked = !config.global; | |||
ShareOverLANItem.Checked = config.shareOverLan; | |||
AutoStartupItem.Checked = AutoStartup.Check(); | |||
AvailabilityStatistics.Checked = config.availabilityStatistics; | |||
onlinePACItem.Checked = onlinePACItem.Enabled && config.useOnlinePac; | |||
localPACItem.Checked = !onlinePACItem.Checked; | |||
UpdatePACItemsEnabledStatus(); | |||
UpdateUpdateMenu(); | |||
} | |||
private void UpdateServersMenu() | |||
@@ -301,7 +342,6 @@ namespace Shadowsocks.View | |||
{ | |||
item.Checked = true; | |||
} | |||
} | |||
} | |||
@@ -319,10 +359,36 @@ namespace Shadowsocks.View | |||
} | |||
} | |||
private void ShowLogForms() | |||
{ | |||
if (logForms.Count == 0) | |||
{ | |||
LogForm f = new LogForm(controller, Logging.LogFilePath); | |||
f.Show(); | |||
f.FormClosed += logForm_FormClosed; | |||
logForms.Add(f); | |||
logFormsVisible = true; | |||
} | |||
else | |||
{ | |||
logFormsVisible = !logFormsVisible; | |||
foreach (LogForm f in logForms) | |||
{ | |||
f.Visible = logFormsVisible; | |||
} | |||
} | |||
} | |||
void logForm_FormClosed(object sender, FormClosedEventArgs e) | |||
{ | |||
logForms.Remove((LogForm)sender); | |||
} | |||
void configForm_FormClosed(object sender, FormClosedEventArgs e) | |||
{ | |||
configForm = null; | |||
Util.Utils.ReleaseMemory(true); | |||
Utils.ReleaseMemory(true); | |||
ShowFirstTimeBalloon(); | |||
} | |||
@@ -343,7 +409,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; | |||
@@ -355,6 +421,18 @@ namespace Shadowsocks.View | |||
Process.Start("https://github.com/shadowsocks/shadowsocks-windows"); | |||
} | |||
private void notifyIcon1_Click(object sender, MouseEventArgs e) | |||
{ | |||
if (e.Button == MouseButtons.Left) | |||
{ | |||
// TODO: show something interesting | |||
} | |||
else if (e.Button == MouseButtons.Middle) | |||
{ | |||
ShowLogForms(); | |||
} | |||
} | |||
private void notifyIcon1_DoubleClick(object sender, MouseEventArgs e) | |||
{ | |||
if (e.Button == MouseButtons.Left) | |||
@@ -413,9 +491,17 @@ namespace Shadowsocks.View | |||
private void ShowLogItem_Click(object sender, EventArgs e) | |||
{ | |||
string argument = Logging.LogFile; | |||
LogForm f = new LogForm(controller, Logging.LogFilePath); | |||
f.Show(); | |||
f.FormClosed += logForm_FormClosed; | |||
new LogForm(argument).Show(); | |||
logForms.Add(f); | |||
} | |||
private void StatisticsConfigItem_Click(object sender, EventArgs e) | |||
{ | |||
StatisticsStrategyConfigurationForm form = new StatisticsStrategyConfigurationForm(controller); | |||
form.Show(); | |||
} | |||
private void QRCodeItem_Click(object sender, EventArgs e) | |||
@@ -520,18 +606,15 @@ namespace Shadowsocks.View | |||
Process.Start(_urlToOpen); | |||
} | |||
private void AutoStartupItem_Click(object sender, EventArgs e) { | |||
private void AutoStartupItem_Click(object sender, EventArgs e) | |||
{ | |||
AutoStartupItem.Checked = !AutoStartupItem.Checked; | |||
if (!AutoStartup.Set(AutoStartupItem.Checked)) { | |||
if (!AutoStartup.Set(AutoStartupItem.Checked)) | |||
{ | |||
MessageBox.Show(I18N.GetString("Failed to update registry")); | |||
} | |||
} | |||
private void AvailabilityStatisticsItem_Click(object sender, EventArgs e) { | |||
AvailabilityStatistics.Checked = !AvailabilityStatistics.Checked; | |||
controller.ToggleAvailabilityStatistics(AvailabilityStatistics.Checked); | |||
} | |||
private void LocalPACItem_Click(object sender, EventArgs e) | |||
{ | |||
if (!localPACItem.Checked) | |||
@@ -591,5 +674,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()); | |||
} | |||
} | |||
} |
@@ -0,0 +1,531 @@ | |||
namespace Shadowsocks.View | |||
{ | |||
partial class StatisticsStrategyConfigurationForm | |||
{ | |||
/// <summary> | |||
/// Required designer variable. | |||
/// </summary> | |||
private System.ComponentModel.IContainer components = null; | |||
/// <summary> | |||
/// Clean up any resources being used. | |||
/// </summary> | |||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> | |||
protected override void Dispose(bool disposing) | |||
{ | |||
if (disposing && (components != null)) | |||
{ | |||
components.Dispose(); | |||
} | |||
base.Dispose(disposing); | |||
} | |||
#region Windows Form Designer generated code | |||
/// <summary> | |||
/// Required method for Designer support - do not modify | |||
/// the contents of this method with the code editor. | |||
/// </summary> | |||
private void InitializeComponent() | |||
{ | |||
this.components = new System.ComponentModel.Container(); | |||
System.Windows.Forms.DataVisualization.Charting.ChartArea chartArea1 = new System.Windows.Forms.DataVisualization.Charting.ChartArea(); | |||
System.Windows.Forms.DataVisualization.Charting.Legend legend1 = new System.Windows.Forms.DataVisualization.Charting.Legend(); | |||
System.Windows.Forms.DataVisualization.Charting.Series series1 = new System.Windows.Forms.DataVisualization.Charting.Series(); | |||
System.Windows.Forms.DataVisualization.Charting.Series series2 = new System.Windows.Forms.DataVisualization.Charting.Series(); | |||
this.StatisticsChart = new System.Windows.Forms.DataVisualization.Charting.Chart(); | |||
this.byISPCheckBox = new System.Windows.Forms.CheckBox(); | |||
this.label2 = new System.Windows.Forms.Label(); | |||
this.label3 = new System.Windows.Forms.Label(); | |||
this.label4 = new System.Windows.Forms.Label(); | |||
this.chartModeSelector = new System.Windows.Forms.GroupBox(); | |||
this.allMode = new System.Windows.Forms.RadioButton(); | |||
this.dayMode = new System.Windows.Forms.RadioButton(); | |||
this.splitContainer1 = new System.Windows.Forms.SplitContainer(); | |||
this.splitContainer2 = new System.Windows.Forms.SplitContainer(); | |||
this.label9 = new System.Windows.Forms.Label(); | |||
this.label8 = new System.Windows.Forms.Label(); | |||
this.dataCollectionMinutesNum = new System.Windows.Forms.NumericUpDown(); | |||
this.StatisticsEnabledCheckBox = new System.Windows.Forms.CheckBox(); | |||
this.choiceKeptMinutesNum = new System.Windows.Forms.NumericUpDown(); | |||
this.byHourOfDayCheckBox = new System.Windows.Forms.CheckBox(); | |||
this.repeatTimesNum = new System.Windows.Forms.NumericUpDown(); | |||
this.label6 = new System.Windows.Forms.Label(); | |||
this.splitContainer3 = new System.Windows.Forms.SplitContainer(); | |||
this.label1 = new System.Windows.Forms.Label(); | |||
this.calculationContainer = new System.Windows.Forms.FlowLayoutPanel(); | |||
this.serverSelector = new System.Windows.Forms.ComboBox(); | |||
this.CancelButton = new System.Windows.Forms.Button(); | |||
this.OKButton = new System.Windows.Forms.Button(); | |||
this.bindingConfiguration = new System.Windows.Forms.BindingSource(this.components); | |||
((System.ComponentModel.ISupportInitialize)(this.StatisticsChart)).BeginInit(); | |||
this.chartModeSelector.SuspendLayout(); | |||
((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); | |||
this.splitContainer1.Panel1.SuspendLayout(); | |||
this.splitContainer1.Panel2.SuspendLayout(); | |||
this.splitContainer1.SuspendLayout(); | |||
((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).BeginInit(); | |||
this.splitContainer2.Panel1.SuspendLayout(); | |||
this.splitContainer2.Panel2.SuspendLayout(); | |||
this.splitContainer2.SuspendLayout(); | |||
((System.ComponentModel.ISupportInitialize)(this.dataCollectionMinutesNum)).BeginInit(); | |||
((System.ComponentModel.ISupportInitialize)(this.choiceKeptMinutesNum)).BeginInit(); | |||
((System.ComponentModel.ISupportInitialize)(this.repeatTimesNum)).BeginInit(); | |||
((System.ComponentModel.ISupportInitialize)(this.splitContainer3)).BeginInit(); | |||
this.splitContainer3.Panel1.SuspendLayout(); | |||
this.splitContainer3.Panel2.SuspendLayout(); | |||
this.splitContainer3.SuspendLayout(); | |||
((System.ComponentModel.ISupportInitialize)(this.bindingConfiguration)).BeginInit(); | |||
this.SuspendLayout(); | |||
// | |||
// StatisticsChart | |||
// | |||
this.StatisticsChart.BackColor = System.Drawing.Color.Transparent; | |||
chartArea1.AxisX.MajorGrid.Enabled = false; | |||
chartArea1.AxisY.MajorGrid.Enabled = false; | |||
chartArea1.AxisY2.MajorGrid.Enabled = false; | |||
chartArea1.BackColor = System.Drawing.Color.Transparent; | |||
chartArea1.Name = "DataArea"; | |||
this.StatisticsChart.ChartAreas.Add(chartArea1); | |||
this.StatisticsChart.Dock = System.Windows.Forms.DockStyle.Fill; | |||
legend1.BackColor = System.Drawing.Color.Transparent; | |||
legend1.Name = "ChartLegend"; | |||
this.StatisticsChart.Legends.Add(legend1); | |||
this.StatisticsChart.Location = new System.Drawing.Point(0, 0); | |||
this.StatisticsChart.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10); | |||
this.StatisticsChart.Name = "StatisticsChart"; | |||
this.StatisticsChart.Palette = System.Windows.Forms.DataVisualization.Charting.ChartColorPalette.Pastel; | |||
series1.ChartArea = "DataArea"; | |||
series1.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Bubble; | |||
series1.Color = System.Drawing.Color.FromArgb(((int)(((byte)(221)))), ((int)(((byte)(88)))), ((int)(((byte)(0))))); | |||
series1.Legend = "ChartLegend"; | |||
series1.Name = "Package Loss"; | |||
series1.XValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.DateTime; | |||
series1.YValuesPerPoint = 2; | |||
series2.BorderWidth = 4; | |||
series2.ChartArea = "DataArea"; | |||
series2.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Line; | |||
series2.Color = System.Drawing.Color.FromArgb(((int)(((byte)(155)))), ((int)(((byte)(77)))), ((int)(((byte)(150))))); | |||
series2.Legend = "ChartLegend"; | |||
series2.Name = "Ping"; | |||
series2.XValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.DateTime; | |||
this.StatisticsChart.Series.Add(series1); | |||
this.StatisticsChart.Series.Add(series2); | |||
this.StatisticsChart.Size = new System.Drawing.Size(1077, 303); | |||
this.StatisticsChart.TabIndex = 2; | |||
// | |||
// byISPCheckBox | |||
// | |||
this.byISPCheckBox.AutoSize = true; | |||
this.byISPCheckBox.DataBindings.Add(new System.Windows.Forms.Binding("Checked", this.bindingConfiguration, "ByIsp", true)); | |||
this.byISPCheckBox.Location = new System.Drawing.Point(13, 54); | |||
this.byISPCheckBox.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10); | |||
this.byISPCheckBox.Name = "byISPCheckBox"; | |||
this.byISPCheckBox.Size = new System.Drawing.Size(220, 31); | |||
this.byISPCheckBox.TabIndex = 5; | |||
this.byISPCheckBox.Text = "By ISP/geolocation"; | |||
this.byISPCheckBox.UseVisualStyleBackColor = true; | |||
// | |||
// label2 | |||
// | |||
this.label2.AutoSize = true; | |||
this.label2.Location = new System.Drawing.Point(8, 136); | |||
this.label2.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0); | |||
this.label2.Name = "label2"; | |||
this.label2.Size = new System.Drawing.Size(167, 27); | |||
this.label2.TabIndex = 8; | |||
this.label2.Text = "Keep choice for "; | |||
// | |||
// label3 | |||
// | |||
this.label3.AutoSize = true; | |||
this.label3.Location = new System.Drawing.Point(285, 136); | |||
this.label3.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0); | |||
this.label3.Name = "label3"; | |||
this.label3.Size = new System.Drawing.Size(87, 27); | |||
this.label3.TabIndex = 9; | |||
this.label3.Text = "minutes"; | |||
// | |||
// label4 | |||
// | |||
this.label4.AutoSize = true; | |||
this.label4.Location = new System.Drawing.Point(8, 218); | |||
this.label4.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0); | |||
this.label4.Name = "label4"; | |||
this.label4.Size = new System.Drawing.Size(54, 27); | |||
this.label4.TabIndex = 10; | |||
this.label4.Text = "Ping"; | |||
// | |||
// chartModeSelector | |||
// | |||
this.chartModeSelector.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); | |||
this.chartModeSelector.Controls.Add(this.allMode); | |||
this.chartModeSelector.Controls.Add(this.dayMode); | |||
this.chartModeSelector.Location = new System.Drawing.Point(801, 104); | |||
this.chartModeSelector.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10); | |||
this.chartModeSelector.Name = "chartModeSelector"; | |||
this.chartModeSelector.Padding = new System.Windows.Forms.Padding(5, 10, 5, 10); | |||
this.chartModeSelector.Size = new System.Drawing.Size(261, 103); | |||
this.chartModeSelector.TabIndex = 3; | |||
this.chartModeSelector.TabStop = false; | |||
this.chartModeSelector.Text = "Chart Mode"; | |||
this.chartModeSelector.Enter += new System.EventHandler(this.chartModeSelector_Enter); | |||
// | |||
// allMode | |||
// | |||
this.allMode.AutoSize = true; | |||
this.allMode.Checked = true; | |||
this.allMode.Location = new System.Drawing.Point(11, 61); | |||
this.allMode.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10); | |||
this.allMode.Name = "allMode"; | |||
this.allMode.Size = new System.Drawing.Size(58, 31); | |||
this.allMode.TabIndex = 1; | |||
this.allMode.TabStop = true; | |||
this.allMode.Text = "all"; | |||
this.allMode.UseVisualStyleBackColor = true; | |||
this.allMode.CheckedChanged += new System.EventHandler(this.allMode_CheckedChanged); | |||
// | |||
// dayMode | |||
// | |||
this.dayMode.AutoSize = true; | |||
this.dayMode.Location = new System.Drawing.Point(11, 29); | |||
this.dayMode.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10); | |||
this.dayMode.Name = "dayMode"; | |||
this.dayMode.Size = new System.Drawing.Size(73, 31); | |||
this.dayMode.TabIndex = 0; | |||
this.dayMode.Text = "24h"; | |||
this.dayMode.UseVisualStyleBackColor = true; | |||
this.dayMode.CheckedChanged += new System.EventHandler(this.dayMode_CheckedChanged); | |||
// | |||
// splitContainer1 | |||
// | |||
this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; | |||
this.splitContainer1.IsSplitterFixed = true; | |||
this.splitContainer1.Location = new System.Drawing.Point(0, 0); | |||
this.splitContainer1.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10); | |||
this.splitContainer1.Name = "splitContainer1"; | |||
this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal; | |||
// | |||
// splitContainer1.Panel1 | |||
// | |||
this.splitContainer1.Panel1.Controls.Add(this.splitContainer2); | |||
// | |||
// splitContainer1.Panel2 | |||
// | |||
this.splitContainer1.Panel2.Controls.Add(this.serverSelector); | |||
this.splitContainer1.Panel2.Controls.Add(this.CancelButton); | |||
this.splitContainer1.Panel2.Controls.Add(this.OKButton); | |||
this.splitContainer1.Panel2.Controls.Add(this.chartModeSelector); | |||
this.splitContainer1.Panel2.Controls.Add(this.StatisticsChart); | |||
this.splitContainer1.Size = new System.Drawing.Size(1077, 614); | |||
this.splitContainer1.SplitterDistance = 301; | |||
this.splitContainer1.SplitterWidth = 10; | |||
this.splitContainer1.TabIndex = 12; | |||
// | |||
// splitContainer2 | |||
// | |||
this.splitContainer2.Dock = System.Windows.Forms.DockStyle.Fill; | |||
this.splitContainer2.FixedPanel = System.Windows.Forms.FixedPanel.Panel1; | |||
this.splitContainer2.IsSplitterFixed = true; | |||
this.splitContainer2.Location = new System.Drawing.Point(0, 0); | |||
this.splitContainer2.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10); | |||
this.splitContainer2.Name = "splitContainer2"; | |||
// | |||
// splitContainer2.Panel1 | |||
// | |||
this.splitContainer2.Panel1.Controls.Add(this.label9); | |||
this.splitContainer2.Panel1.Controls.Add(this.label8); | |||
this.splitContainer2.Panel1.Controls.Add(this.dataCollectionMinutesNum); | |||
this.splitContainer2.Panel1.Controls.Add(this.StatisticsEnabledCheckBox); | |||
this.splitContainer2.Panel1.Controls.Add(this.choiceKeptMinutesNum); | |||
this.splitContainer2.Panel1.Controls.Add(this.byHourOfDayCheckBox); | |||
this.splitContainer2.Panel1.Controls.Add(this.repeatTimesNum); | |||
this.splitContainer2.Panel1.Controls.Add(this.label6); | |||
this.splitContainer2.Panel1.Controls.Add(this.label2); | |||
this.splitContainer2.Panel1.Controls.Add(this.label4); | |||
this.splitContainer2.Panel1.Controls.Add(this.byISPCheckBox); | |||
this.splitContainer2.Panel1.Controls.Add(this.label3); | |||
// | |||
// splitContainer2.Panel2 | |||
// | |||
this.splitContainer2.Panel2.Controls.Add(this.splitContainer3); | |||
this.splitContainer2.Size = new System.Drawing.Size(1077, 301); | |||
this.splitContainer2.SplitterDistance = 384; | |||
this.splitContainer2.SplitterWidth = 5; | |||
this.splitContainer2.TabIndex = 7; | |||
// | |||
// label9 | |||
// | |||
this.label9.AutoSize = true; | |||
this.label9.Location = new System.Drawing.Point(8, 175); | |||
this.label9.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0); | |||
this.label9.Name = "label9"; | |||
this.label9.Size = new System.Drawing.Size(162, 27); | |||
this.label9.TabIndex = 20; | |||
this.label9.Text = "Collect data per"; | |||
// | |||
// label8 | |||
// | |||
this.label8.AutoSize = true; | |||
this.label8.Font = new System.Drawing.Font("Microsoft YaHei", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); | |||
this.label8.Location = new System.Drawing.Point(285, 176); | |||
this.label8.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0); | |||
this.label8.Name = "label8"; | |||
this.label8.Size = new System.Drawing.Size(87, 27); | |||
this.label8.TabIndex = 19; | |||
this.label8.Text = "minutes"; | |||
// | |||
// dataCollectionMinutesNum | |||
// | |||
this.dataCollectionMinutesNum.DataBindings.Add(new System.Windows.Forms.Binding("Value", this.bindingConfiguration, "DataCollectionMinutes", true)); | |||
this.dataCollectionMinutesNum.Increment = new decimal(new int[] { | |||
10, | |||
0, | |||
0, | |||
0}); | |||
this.dataCollectionMinutesNum.Location = new System.Drawing.Point(176, 173); | |||
this.dataCollectionMinutesNum.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); | |||
this.dataCollectionMinutesNum.Maximum = new decimal(new int[] { | |||
120, | |||
0, | |||
0, | |||
0}); | |||
this.dataCollectionMinutesNum.Minimum = new decimal(new int[] { | |||
5, | |||
0, | |||
0, | |||
0}); | |||
this.dataCollectionMinutesNum.Name = "dataCollectionMinutesNum"; | |||
this.dataCollectionMinutesNum.Size = new System.Drawing.Size(100, 34); | |||
this.dataCollectionMinutesNum.TabIndex = 18; | |||
this.dataCollectionMinutesNum.Value = new decimal(new int[] { | |||
10, | |||
0, | |||
0, | |||
0}); | |||
// | |||
// StatisticsEnabledCheckBox | |||
// | |||
this.StatisticsEnabledCheckBox.AutoSize = true; | |||
this.StatisticsEnabledCheckBox.DataBindings.Add(new System.Windows.Forms.Binding("Checked", this.bindingConfiguration, "StatisticsEnabled", true)); | |||
this.StatisticsEnabledCheckBox.Location = new System.Drawing.Point(13, 12); | |||
this.StatisticsEnabledCheckBox.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10); | |||
this.StatisticsEnabledCheckBox.Name = "StatisticsEnabledCheckBox"; | |||
this.StatisticsEnabledCheckBox.Size = new System.Drawing.Size(189, 31); | |||
this.StatisticsEnabledCheckBox.TabIndex = 17; | |||
this.StatisticsEnabledCheckBox.Text = "Enable Statistics"; | |||
this.StatisticsEnabledCheckBox.UseVisualStyleBackColor = true; | |||
// | |||
// choiceKeptMinutesNum | |||
// | |||
this.choiceKeptMinutesNum.DataBindings.Add(new System.Windows.Forms.Binding("Value", this.bindingConfiguration, "ChoiceKeptMinutes", true)); | |||
this.choiceKeptMinutesNum.Increment = new decimal(new int[] { | |||
10, | |||
0, | |||
0, | |||
0}); | |||
this.choiceKeptMinutesNum.Location = new System.Drawing.Point(176, 134); | |||
this.choiceKeptMinutesNum.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); | |||
this.choiceKeptMinutesNum.Maximum = new decimal(new int[] { | |||
120, | |||
0, | |||
0, | |||
0}); | |||
this.choiceKeptMinutesNum.Minimum = new decimal(new int[] { | |||
5, | |||
0, | |||
0, | |||
0}); | |||
this.choiceKeptMinutesNum.Name = "choiceKeptMinutesNum"; | |||
this.choiceKeptMinutesNum.Size = new System.Drawing.Size(100, 34); | |||
this.choiceKeptMinutesNum.TabIndex = 16; | |||
this.choiceKeptMinutesNum.Value = new decimal(new int[] { | |||
10, | |||
0, | |||
0, | |||
0}); | |||
// | |||
// byHourOfDayCheckBox | |||
// | |||
this.byHourOfDayCheckBox.AutoSize = true; | |||
this.byHourOfDayCheckBox.DataBindings.Add(new System.Windows.Forms.Binding("Checked", this.bindingConfiguration, "ByHourOfDay", true)); | |||
this.byHourOfDayCheckBox.Location = new System.Drawing.Point(13, 95); | |||
this.byHourOfDayCheckBox.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10); | |||
this.byHourOfDayCheckBox.Name = "byHourOfDayCheckBox"; | |||
this.byHourOfDayCheckBox.Size = new System.Drawing.Size(180, 31); | |||
this.byHourOfDayCheckBox.TabIndex = 15; | |||
this.byHourOfDayCheckBox.Text = "By hour of day"; | |||
this.byHourOfDayCheckBox.UseVisualStyleBackColor = true; | |||
// | |||
// repeatTimesNum | |||
// | |||
this.repeatTimesNum.DataBindings.Add(new System.Windows.Forms.Binding("Value", this.bindingConfiguration, "RepeatTimesNum", true)); | |||
this.repeatTimesNum.Location = new System.Drawing.Point(72, 216); | |||
this.repeatTimesNum.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); | |||
this.repeatTimesNum.Maximum = new decimal(new int[] { | |||
10, | |||
0, | |||
0, | |||
0}); | |||
this.repeatTimesNum.Name = "repeatTimesNum"; | |||
this.repeatTimesNum.Size = new System.Drawing.Size(99, 34); | |||
this.repeatTimesNum.TabIndex = 14; | |||
this.repeatTimesNum.Value = new decimal(new int[] { | |||
4, | |||
0, | |||
0, | |||
0}); | |||
// | |||
// label6 | |||
// | |||
this.label6.AutoSize = true; | |||
this.label6.Font = new System.Drawing.Font("Microsoft YaHei", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); | |||
this.label6.Location = new System.Drawing.Point(178, 218); | |||
this.label6.Name = "label6"; | |||
this.label6.Size = new System.Drawing.Size(201, 27); | |||
this.label6.TabIndex = 13; | |||
this.label6.Text = "packages everytime"; | |||
// | |||
// splitContainer3 | |||
// | |||
this.splitContainer3.Dock = System.Windows.Forms.DockStyle.Fill; | |||
this.splitContainer3.FixedPanel = System.Windows.Forms.FixedPanel.Panel1; | |||
this.splitContainer3.IsSplitterFixed = true; | |||
this.splitContainer3.Location = new System.Drawing.Point(0, 0); | |||
this.splitContainer3.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10); | |||
this.splitContainer3.Name = "splitContainer3"; | |||
this.splitContainer3.Orientation = System.Windows.Forms.Orientation.Horizontal; | |||
// | |||
// splitContainer3.Panel1 | |||
// | |||
this.splitContainer3.Panel1.Controls.Add(this.label1); | |||
// | |||
// splitContainer3.Panel2 | |||
// | |||
this.splitContainer3.Panel2.Controls.Add(this.calculationContainer); | |||
this.splitContainer3.Size = new System.Drawing.Size(688, 301); | |||
this.splitContainer3.SplitterDistance = 46; | |||
this.splitContainer3.SplitterWidth = 10; | |||
this.splitContainer3.TabIndex = 6; | |||
// | |||
// label1 | |||
// | |||
this.label1.AutoSize = true; | |||
this.label1.Location = new System.Drawing.Point(5, 12); | |||
this.label1.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0); | |||
this.label1.Name = "label1"; | |||
this.label1.Size = new System.Drawing.Size(262, 27); | |||
this.label1.TabIndex = 0; | |||
this.label1.Text = "Design evaluation method"; | |||
// | |||
// calculationContainer | |||
// | |||
this.calculationContainer.AutoScroll = true; | |||
this.calculationContainer.Dock = System.Windows.Forms.DockStyle.Fill; | |||
this.calculationContainer.Location = new System.Drawing.Point(0, 0); | |||
this.calculationContainer.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10); | |||
this.calculationContainer.Name = "calculationContainer"; | |||
this.calculationContainer.Size = new System.Drawing.Size(688, 245); | |||
this.calculationContainer.TabIndex = 1; | |||
// | |||
// serverSelector | |||
// | |||
this.serverSelector.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); | |||
this.serverSelector.FormattingEnabled = true; | |||
this.serverSelector.Location = new System.Drawing.Point(801, 67); | |||
this.serverSelector.Name = "serverSelector"; | |||
this.serverSelector.Size = new System.Drawing.Size(260, 35); | |||
this.serverSelector.TabIndex = 6; | |||
this.serverSelector.SelectedIndexChanged += new System.EventHandler(this.serverSelector_SelectedIndexChanged); | |||
// | |||
// CancelButton | |||
// | |||
this.CancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); | |||
this.CancelButton.Location = new System.Drawing.Point(960, 220); | |||
this.CancelButton.Name = "CancelButton"; | |||
this.CancelButton.Size = new System.Drawing.Size(101, 41); | |||
this.CancelButton.TabIndex = 5; | |||
this.CancelButton.Text = "Cancel"; | |||
this.CancelButton.UseVisualStyleBackColor = true; | |||
this.CancelButton.Click += new System.EventHandler(this.CancelButton_Click); | |||
// | |||
// OKButton | |||
// | |||
this.OKButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); | |||
this.OKButton.Location = new System.Drawing.Point(852, 220); | |||
this.OKButton.Name = "OKButton"; | |||
this.OKButton.Size = new System.Drawing.Size(101, 41); | |||
this.OKButton.TabIndex = 4; | |||
this.OKButton.Text = "OK"; | |||
this.OKButton.UseVisualStyleBackColor = true; | |||
this.OKButton.Click += new System.EventHandler(this.OKButton_Click); | |||
// | |||
// bindingConfiguration | |||
// | |||
this.bindingConfiguration.DataSource = typeof(Shadowsocks.Model.StatisticsStrategyConfiguration); | |||
// | |||
// StatisticsStrategyConfigurationForm | |||
// | |||
this.AutoScaleDimensions = new System.Drawing.SizeF(12F, 27F); | |||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; | |||
this.AutoSize = true; | |||
this.ClientSize = new System.Drawing.Size(1077, 614); | |||
this.Controls.Add(this.splitContainer1); | |||
this.Font = new System.Drawing.Font("Microsoft YaHei", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); | |||
this.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10); | |||
this.MinimumSize = new System.Drawing.Size(1059, 498); | |||
this.Name = "StatisticsStrategyConfigurationForm"; | |||
this.Text = "StatisticsStrategyConfigurationForm"; | |||
((System.ComponentModel.ISupportInitialize)(this.StatisticsChart)).EndInit(); | |||
this.chartModeSelector.ResumeLayout(false); | |||
this.chartModeSelector.PerformLayout(); | |||
this.splitContainer1.Panel1.ResumeLayout(false); | |||
this.splitContainer1.Panel2.ResumeLayout(false); | |||
((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); | |||
this.splitContainer1.ResumeLayout(false); | |||
this.splitContainer2.Panel1.ResumeLayout(false); | |||
this.splitContainer2.Panel1.PerformLayout(); | |||
this.splitContainer2.Panel2.ResumeLayout(false); | |||
((System.ComponentModel.ISupportInitialize)(this.splitContainer2)).EndInit(); | |||
this.splitContainer2.ResumeLayout(false); | |||
((System.ComponentModel.ISupportInitialize)(this.dataCollectionMinutesNum)).EndInit(); | |||
((System.ComponentModel.ISupportInitialize)(this.choiceKeptMinutesNum)).EndInit(); | |||
((System.ComponentModel.ISupportInitialize)(this.repeatTimesNum)).EndInit(); | |||
this.splitContainer3.Panel1.ResumeLayout(false); | |||
this.splitContainer3.Panel1.PerformLayout(); | |||
this.splitContainer3.Panel2.ResumeLayout(false); | |||
((System.ComponentModel.ISupportInitialize)(this.splitContainer3)).EndInit(); | |||
this.splitContainer3.ResumeLayout(false); | |||
((System.ComponentModel.ISupportInitialize)(this.bindingConfiguration)).EndInit(); | |||
this.ResumeLayout(false); | |||
} | |||
#endregion | |||
private System.Windows.Forms.DataVisualization.Charting.Chart StatisticsChart; | |||
private System.Windows.Forms.CheckBox byISPCheckBox; | |||
private System.Windows.Forms.Label label2; | |||
private System.Windows.Forms.Label label3; | |||
private System.Windows.Forms.Label label4; | |||
private System.Windows.Forms.GroupBox chartModeSelector; | |||
private System.Windows.Forms.RadioButton allMode; | |||
private System.Windows.Forms.RadioButton dayMode; | |||
private System.Windows.Forms.SplitContainer splitContainer1; | |||
private System.Windows.Forms.Label label1; | |||
private System.Windows.Forms.SplitContainer splitContainer2; | |||
private System.Windows.Forms.FlowLayoutPanel calculationContainer; | |||
private System.Windows.Forms.SplitContainer splitContainer3; | |||
private System.Windows.Forms.NumericUpDown repeatTimesNum; | |||
private System.Windows.Forms.Label label6; | |||
private System.Windows.Forms.CheckBox byHourOfDayCheckBox; | |||
private System.Windows.Forms.NumericUpDown choiceKeptMinutesNum; | |||
private System.Windows.Forms.CheckBox StatisticsEnabledCheckBox; | |||
private System.Windows.Forms.Label label9; | |||
private System.Windows.Forms.Label label8; | |||
private System.Windows.Forms.NumericUpDown dataCollectionMinutesNum; | |||
private System.Windows.Forms.BindingSource bindingConfiguration; | |||
private new System.Windows.Forms.Button CancelButton; | |||
private System.Windows.Forms.Button OKButton; | |||
private System.Windows.Forms.ComboBox serverSelector; | |||
} | |||
} |
@@ -0,0 +1,141 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Data; | |||
using System.Linq; | |||
using System.Net.NetworkInformation; | |||
using System.Windows.Forms; | |||
using Shadowsocks.Controller; | |||
using Shadowsocks.Model; | |||
namespace Shadowsocks.View | |||
{ | |||
public partial class StatisticsStrategyConfigurationForm : Form | |||
{ | |||
private readonly ShadowsocksController _controller; | |||
private StatisticsStrategyConfiguration _configuration; | |||
private DataTable _dataTable = new DataTable(); | |||
private List<string> _servers; | |||
public StatisticsStrategyConfigurationForm(ShadowsocksController controller) | |||
{ | |||
if (controller == null) return; | |||
InitializeComponent(); | |||
_controller = controller; | |||
_controller.ConfigChanged += (sender, args) => LoadConfiguration(); | |||
LoadConfiguration(); | |||
Load += (sender, args) => InitData(); | |||
} | |||
private void LoadConfiguration() | |||
{ | |||
var configs = _controller.GetCurrentConfiguration().configs; | |||
_servers = configs.Select(server => server.FriendlyName()).ToList(); | |||
_configuration = _controller.StatisticsConfiguration | |||
?? new StatisticsStrategyConfiguration(); | |||
if (_configuration.Calculations == null) | |||
{ | |||
_configuration = new StatisticsStrategyConfiguration(); | |||
} | |||
} | |||
private void InitData() | |||
{ | |||
bindingConfiguration.Add(_configuration); | |||
foreach (var kv in _configuration.Calculations) | |||
{ | |||
var calculation = new CalculationControl(kv.Key, kv.Value); | |||
calculationContainer.Controls.Add(calculation); | |||
} | |||
serverSelector.DataSource = _servers; | |||
_dataTable.Columns.Add("Timestamp", typeof(DateTime)); | |||
_dataTable.Columns.Add("Package Loss", typeof(int)); | |||
_dataTable.Columns.Add("Ping", typeof(int)); | |||
StatisticsChart.Series["Package Loss"].XValueMember = "Timestamp"; | |||
StatisticsChart.Series["Package Loss"].YValueMembers = "Package Loss"; | |||
StatisticsChart.Series["Ping"].XValueMember = "Timestamp"; | |||
StatisticsChart.Series["Ping"].YValueMembers = "Ping"; | |||
StatisticsChart.DataSource = _dataTable; | |||
loadChartData(); | |||
StatisticsChart.DataBind(); | |||
} | |||
private void CancelButton_Click(object sender, EventArgs e) | |||
{ | |||
Close(); | |||
} | |||
private void OKButton_Click(object sender, EventArgs e) | |||
{ | |||
foreach (CalculationControl calculation in calculationContainer.Controls) | |||
{ | |||
_configuration.Calculations[calculation.Value] = calculation.Factor; | |||
} | |||
_controller?.SaveStrategyConfigurations(_configuration); | |||
_controller?.UpdateStatisticsConfiguration(StatisticsEnabledCheckBox.Checked); | |||
Close(); | |||
} | |||
private void loadChartData() | |||
{ | |||
string serverName = _servers[serverSelector.SelectedIndex]; | |||
_dataTable.Rows.Clear(); | |||
//return directly when no data is usable | |||
if (_controller.availabilityStatistics?.FilteredStatistics == null) return; | |||
List<AvailabilityStatistics.RawStatisticsData> statistics; | |||
if (!_controller.availabilityStatistics.FilteredStatistics.TryGetValue(serverName, out statistics)) return; | |||
IEnumerable<IGrouping<int, AvailabilityStatistics.RawStatisticsData>> dataGroups; | |||
if (allMode.Checked) | |||
{ | |||
dataGroups = statistics.GroupBy(data => data.Timestamp.DayOfYear); | |||
StatisticsChart.ChartAreas["DataArea"].AxisX.LabelStyle.Format = "MM/dd/yyyy"; | |||
StatisticsChart.ChartAreas["DataArea"].AxisX2.LabelStyle.Format = "MM/dd/yyyy"; | |||
} | |||
else | |||
{ | |||
dataGroups = statistics.GroupBy(data => data.Timestamp.Hour); | |||
StatisticsChart.ChartAreas["DataArea"].AxisX.LabelStyle.Format = "HH:00"; | |||
StatisticsChart.ChartAreas["DataArea"].AxisX2.LabelStyle.Format = "HH:00"; | |||
} | |||
var finalData = from dataGroup in dataGroups | |||
orderby dataGroup.Key | |||
select new | |||
{ | |||
Timestamp = dataGroup.First().Timestamp, | |||
Ping = (int)dataGroup.Average(data => data.RoundtripTime), | |||
PackageLoss = (int) | |||
(dataGroup.Count(data => data.ICMPStatus.Equals(IPStatus.TimedOut.ToString())) | |||
/ (float)dataGroup.Count() * 100) | |||
}; | |||
foreach (var data in finalData) | |||
{ | |||
_dataTable.Rows.Add(data.Timestamp, data.PackageLoss, data.Ping); | |||
} | |||
StatisticsChart.DataBind(); | |||
} | |||
private void serverSelector_SelectedIndexChanged(object sender, EventArgs e) | |||
{ | |||
loadChartData(); | |||
} | |||
private void chartModeSelector_Enter(object sender, EventArgs e) | |||
{ | |||
} | |||
private void dayMode_CheckedChanged(object sender, EventArgs e) | |||
{ | |||
loadChartData(); | |||
} | |||
private void allMode_CheckedChanged(object sender, EventArgs e) | |||
{ | |||
loadChartData(); | |||
} | |||
} | |||
} |
@@ -0,0 +1,126 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<root> | |||
<!-- | |||
Microsoft ResX Schema | |||
Version 2.0 | |||
The primary goals of this format is to allow a simple XML format | |||
that is mostly human readable. The generation and parsing of the | |||
various data types are done through the TypeConverter classes | |||
associated with the data types. | |||
Example: | |||
... ado.net/XML headers & schema ... | |||
<resheader name="resmimetype">text/microsoft-resx</resheader> | |||
<resheader name="version">2.0</resheader> | |||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> | |||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> | |||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> | |||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> | |||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> | |||
<value>[base64 mime encoded serialized .NET Framework object]</value> | |||
</data> | |||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> | |||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> | |||
<comment>This is a comment</comment> | |||
</data> | |||
There are any number of "resheader" rows that contain simple | |||
name/value pairs. | |||
Each data row contains a name, and value. The row also contains a | |||
type or mimetype. Type corresponds to a .NET class that support | |||
text/value conversion through the TypeConverter architecture. | |||
Classes that don't support this are serialized and stored with the | |||
mimetype set. | |||
The mimetype is used for serialized objects, and tells the | |||
ResXResourceReader how to depersist the object. This is currently not | |||
extensible. For a given mimetype the value must be set accordingly: | |||
Note - application/x-microsoft.net.object.binary.base64 is the format | |||
that the ResXResourceWriter will generate, however the reader can | |||
read any of the formats listed below. | |||
mimetype: application/x-microsoft.net.object.binary.base64 | |||
value : The object must be serialized with | |||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter | |||
: and then encoded with base64 encoding. | |||
mimetype: application/x-microsoft.net.object.soap.base64 | |||
value : The object must be serialized with | |||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter | |||
: and then encoded with base64 encoding. | |||
mimetype: application/x-microsoft.net.object.bytearray.base64 | |||
value : The object must be serialized into a byte array | |||
: using a System.ComponentModel.TypeConverter | |||
: and then encoded with base64 encoding. | |||
--> | |||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> | |||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> | |||
<xsd:element name="root" msdata:IsDataSet="true"> | |||
<xsd:complexType> | |||
<xsd:choice maxOccurs="unbounded"> | |||
<xsd:element name="metadata"> | |||
<xsd:complexType> | |||
<xsd:sequence> | |||
<xsd:element name="value" type="xsd:string" minOccurs="0" /> | |||
</xsd:sequence> | |||
<xsd:attribute name="name" use="required" type="xsd:string" /> | |||
<xsd:attribute name="type" type="xsd:string" /> | |||
<xsd:attribute name="mimetype" type="xsd:string" /> | |||
<xsd:attribute ref="xml:space" /> | |||
</xsd:complexType> | |||
</xsd:element> | |||
<xsd:element name="assembly"> | |||
<xsd:complexType> | |||
<xsd:attribute name="alias" type="xsd:string" /> | |||
<xsd:attribute name="name" type="xsd:string" /> | |||
</xsd:complexType> | |||
</xsd:element> | |||
<xsd:element name="data"> | |||
<xsd:complexType> | |||
<xsd:sequence> | |||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | |||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> | |||
</xsd:sequence> | |||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> | |||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> | |||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> | |||
<xsd:attribute ref="xml:space" /> | |||
</xsd:complexType> | |||
</xsd:element> | |||
<xsd:element name="resheader"> | |||
<xsd:complexType> | |||
<xsd:sequence> | |||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | |||
</xsd:sequence> | |||
<xsd:attribute name="name" type="xsd:string" use="required" /> | |||
</xsd:complexType> | |||
</xsd:element> | |||
</xsd:choice> | |||
</xsd:complexType> | |||
</xsd:element> | |||
</xsd:schema> | |||
<resheader name="resmimetype"> | |||
<value>text/microsoft-resx</value> | |||
</resheader> | |||
<resheader name="version"> | |||
<value>2.0</value> | |||
</resheader> | |||
<resheader name="reader"> | |||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||
</resheader> | |||
<resheader name="writer"> | |||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | |||
</resheader> | |||
<metadata name="bindingConfiguration.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"> | |||
<value>1, 30</value> | |||
</metadata> | |||
<metadata name="$this.TrayHeight" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"> | |||
<value>63</value> | |||
</metadata> | |||
</root> |
@@ -1,6 +1,19 @@ | |||
<?xml version="1.0"?> | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<configuration> | |||
<startup> | |||
<supportedRuntime version="v4.0"/> | |||
<supportedRuntime version="v2.0.50727"/> | |||
</startup></configuration> | |||
<supportedRuntime version="v4.0" /> | |||
<supportedRuntime version="v2.0.50727" /> | |||
</startup> | |||
<runtime> | |||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> | |||
<dependentAssembly> | |||
<assemblyIdentity name="System.Runtime" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" /> | |||
<bindingRedirect oldVersion="0.0.0.0-2.6.10.0" newVersion="2.6.10.0" /> | |||
</dependentAssembly> | |||
<dependentAssembly> | |||
<assemblyIdentity name="System.Threading.Tasks" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" /> | |||
<bindingRedirect oldVersion="0.0.0.0-2.6.10.0" newVersion="2.6.10.0" /> | |||
</dependentAssembly> | |||
</assemblyBinding> | |||
</runtime> | |||
</configuration> |
@@ -10,7 +10,7 @@ | |||
</trustInfo> | |||
<asmv3:application> | |||
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings"> | |||
<dpiAware>true</dpiAware> | |||
<dpiAware>True/PM</dpiAware> | |||
</asmv3:windowsSettings> | |||
</asmv3:application> | |||
</assembly> |
@@ -0,0 +1,11 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<packages> | |||
<package id="Costura.Fody" version="1.3.3.0" targetFramework="net4-client" developmentDependency="true" /> | |||
<package id="Fody" version="1.29.4" targetFramework="net40-client" developmentDependency="true" /> | |||
<package id="Microsoft.Bcl" version="1.1.10" targetFramework="net40-client" /> | |||
<package id="Microsoft.Bcl.Async" version="1.0.168" targetFramework="net40-client" /> | |||
<package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net40-client" /> | |||
<package id="Microsoft.Net.Http" version="2.0.20710.0" targetFramework="net4-client" /> | |||
<package id="Newtonsoft.Json" version="8.0.2" targetFramework="net40-client" /> | |||
<package id="System.Net.Http" version="2.0.20710.0" targetFramework="net40-client" /> | |||
</packages> |
@@ -36,6 +36,8 @@ | |||
<ApplicationVersion>1.0.0.%2a</ApplicationVersion> | |||
<UseApplicationTrust>false</UseApplicationTrust> | |||
<BootstrapperEnabled>true</BootstrapperEnabled> | |||
<NuGetPackageImportStamp> | |||
</NuGetPackageImportStamp> | |||
</PropertyGroup> | |||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'"> | |||
<DebugSymbols>true</DebugSymbols> | |||
@@ -46,6 +48,7 @@ | |||
<ErrorReport>prompt</ErrorReport> | |||
<CodeAnalysisRuleSet>ManagedMinimumRules.ruleset</CodeAnalysisRuleSet> | |||
<Prefer32Bit>false</Prefer32Bit> | |||
<Optimize>false</Optimize> | |||
</PropertyGroup> | |||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'"> | |||
<OutputPath>bin\x86\Release\</OutputPath> | |||
@@ -62,12 +65,57 @@ | |||
<ApplicationManifest>app.manifest</ApplicationManifest> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<Reference Include="Microsoft.CSharp" /> | |||
<Reference Include="Microsoft.Threading.Tasks, Version=1.0.12.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> | |||
<HintPath>3rd\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll</HintPath> | |||
<Private>True</Private> | |||
</Reference> | |||
<Reference Include="Microsoft.Threading.Tasks.Extensions, Version=1.0.12.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> | |||
<HintPath>3rd\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll</HintPath> | |||
<Private>True</Private> | |||
</Reference> | |||
<Reference Include="Microsoft.Threading.Tasks.Extensions.Desktop, Version=1.0.168.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> | |||
<HintPath>3rd\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll</HintPath> | |||
<Private>True</Private> | |||
</Reference> | |||
<Reference Include="Microsoft.VisualBasic" /> | |||
<Reference Include="Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> | |||
<HintPath>3rd\Newtonsoft.Json.8.0.2\lib\net40\Newtonsoft.Json.dll</HintPath> | |||
<Private>True</Private> | |||
</Reference> | |||
<Reference Include="PresentationCore" /> | |||
<Reference Include="PresentationFramework" /> | |||
<Reference Include="System" /> | |||
<Reference Include="System.Data" /> | |||
<Reference Include="System.Drawing" /> | |||
<Reference Include="System.IO, Version=2.6.10.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> | |||
<HintPath>3rd\Microsoft.Bcl.1.1.10\lib\net40\System.IO.dll</HintPath> | |||
<Private>True</Private> | |||
</Reference> | |||
<Reference Include="System.Net" /> | |||
<Reference Include="System.Net.Http, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> | |||
<HintPath>3rd\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll</HintPath> | |||
<Private>True</Private> | |||
</Reference> | |||
<Reference Include="System.Net.Http.WebRequest, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> | |||
<HintPath>3rd\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll</HintPath> | |||
<Private>True</Private> | |||
</Reference> | |||
<Reference Include="System.Runtime, Version=2.6.10.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> | |||
<HintPath>3rd\Microsoft.Bcl.1.1.10\lib\net40\System.Runtime.dll</HintPath> | |||
<Private>True</Private> | |||
</Reference> | |||
<Reference Include="System.Threading.Tasks, Version=2.6.10.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> | |||
<HintPath>3rd\Microsoft.Bcl.1.1.10\lib\net40\System.Threading.Tasks.dll</HintPath> | |||
<Private>True</Private> | |||
</Reference> | |||
<Reference Include="System.Windows.Forms" /> | |||
<Reference Include="System.Windows.Forms.DataVisualization" /> | |||
<Reference Include="System.Xaml" /> | |||
<Reference Include="System.XML" /> | |||
<Reference Include="UIAutomationProvider" /> | |||
<Reference Include="WindowsBase" /> | |||
<Reference Include="WindowsFormsIntegration" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<Compile Include="3rd\zxing\BarcodeFormat.cs" /> | |||
@@ -116,7 +164,6 @@ | |||
<Compile Include="3rd\zxing\qrcode\encoder\MaskUtil.cs" /> | |||
<Compile Include="3rd\zxing\qrcode\encoder\MatrixUtil.cs" /> | |||
<Compile Include="3rd\zxing\qrcode\encoder\QRCode.cs" /> | |||
<Compile Include="3rd\SimpleJson.cs" /> | |||
<Compile Include="3rd\zxing\qrcode\QRCodeReader.cs" /> | |||
<Compile Include="3rd\zxing\Result.cs" /> | |||
<Compile Include="3rd\zxing\ResultMetadataType.cs" /> | |||
@@ -125,7 +172,7 @@ | |||
<Compile Include="3rd\zxing\WriterException.cs" /> | |||
<Compile Include="Controller\Service\AvailabilityStatistics.cs" /> | |||
<Compile Include="Controller\Strategy\HighAvailabilityStrategy.cs" /> | |||
<Compile Include="Controller\Strategy\SimplyChooseByStatisticsStrategy.cs" /> | |||
<Compile Include="Controller\Strategy\StatisticsStrategy.cs" /> | |||
<Compile Include="Controller\System\AutoStartup.cs" /> | |||
<Compile Include="Controller\FileManager.cs" /> | |||
<Compile Include="Controller\Service\GFWListUpdater.cs" /> | |||
@@ -138,15 +185,17 @@ | |||
<Compile Include="Encryption\EncryptorBase.cs" /> | |||
<Compile Include="Encryption\EncryptorFactory.cs" /> | |||
<Compile Include="Encryption\IVEncryptor.cs" /> | |||
<Compile Include="Encryption\MbedTLS.cs" /> | |||
<Compile Include="Encryption\PolarSSL.cs" /> | |||
<Compile Include="Encryption\PolarSSLEncryptor.cs" /> | |||
<Compile Include="Encryption\Sodium.cs" /> | |||
<Compile Include="Encryption\SodiumEncryptor.cs" /> | |||
<Compile Include="Encryption\TableEncryptor.cs" /> | |||
<Compile Include="Encryption\IEncryptor.cs" /> | |||
<Compile Include="Controller\Service\PACServer.cs" /> | |||
<Compile Include="Model\LogViewerConfig.cs" /> | |||
<Compile Include="Model\Server.cs" /> | |||
<Compile Include="Model\Configuration.cs" /> | |||
<Compile Include="Model\StatisticsStrategyConfiguration.cs" /> | |||
<Compile Include="Properties\Resources.Designer.cs"> | |||
<AutoGen>True</AutoGen> | |||
<DesignTime>True</DesignTime> | |||
@@ -168,6 +217,12 @@ | |||
<Compile Include="Properties\AssemblyInfo.cs" /> | |||
<Compile Include="Controller\ShadowsocksController.cs" /> | |||
<Compile Include="Controller\System\SystemProxy.cs" /> | |||
<Compile Include="View\CalculationControl.cs"> | |||
<SubType>UserControl</SubType> | |||
</Compile> | |||
<Compile Include="View\CalculationControl.Designer.cs"> | |||
<DependentUpon>CalculationControl.cs</DependentUpon> | |||
</Compile> | |||
<Compile Include="View\LogForm.cs"> | |||
<SubType>Form</SubType> | |||
</Compile> | |||
@@ -184,6 +239,12 @@ | |||
<Compile Include="View\QRCodeSplashForm.cs"> | |||
<SubType>Form</SubType> | |||
</Compile> | |||
<Compile Include="View\StatisticsStrategyConfigurationForm.cs"> | |||
<SubType>Form</SubType> | |||
</Compile> | |||
<Compile Include="View\StatisticsStrategyConfigurationForm.Designer.cs"> | |||
<DependentUpon>StatisticsStrategyConfigurationForm.cs</DependentUpon> | |||
</Compile> | |||
<EmbeddedResource Include="View\ConfigForm.resx"> | |||
<DependentUpon>ConfigForm.cs</DependentUpon> | |||
<SubType>Designer</SubType> | |||
@@ -193,12 +254,18 @@ | |||
<SubType>Designer</SubType> | |||
<LastGenOutput>Resources.Designer.cs</LastGenOutput> | |||
</EmbeddedResource> | |||
<EmbeddedResource Include="View\CalculationControl.resx"> | |||
<DependentUpon>CalculationControl.cs</DependentUpon> | |||
</EmbeddedResource> | |||
<EmbeddedResource Include="View\LogForm.resx"> | |||
<DependentUpon>LogForm.cs</DependentUpon> | |||
</EmbeddedResource> | |||
<EmbeddedResource Include="View\QRCodeForm.resx"> | |||
<DependentUpon>QRCodeForm.cs</DependentUpon> | |||
</EmbeddedResource> | |||
<EmbeddedResource Include="View\StatisticsStrategyConfigurationForm.resx"> | |||
<DependentUpon>StatisticsStrategyConfigurationForm.cs</DependentUpon> | |||
</EmbeddedResource> | |||
<None Include="app.config" /> | |||
<None Include="app.manifest"> | |||
<SubType>Designer</SubType> | |||
@@ -210,6 +277,8 @@ | |||
<None Include="Data\proxy.pac.txt.gz" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<None Include="packages.config" /> | |||
<None Include="Properties\DataSources\Shadowsocks.Model.StatisticsStrategyConfiguration.datasource" /> | |||
<None Include="Resources\ss20.png" /> | |||
<None Include="Resources\ss16.png" /> | |||
<None Include="Resources\ss24.png" /> | |||
@@ -217,6 +286,9 @@ | |||
<Content Include="Data\cn.txt" /> | |||
<Content Include="Data\privoxy_conf.txt" /> | |||
<Content Include="Data\user-rule.txt" /> | |||
<Content Include="FodyWeavers.xml"> | |||
<SubType>Designer</SubType> | |||
</Content> | |||
<Content Include="shadowsocks.ico" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
@@ -252,6 +324,15 @@ | |||
</BootstrapperPackage> | |||
</ItemGroup> | |||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | |||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> | |||
<PropertyGroup> | |||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText> | |||
</PropertyGroup> | |||
<Error Condition="!Exists('3rd\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" Text="$([System.String]::Format('$(ErrorText)', '3rd\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets'))" /> | |||
<Error Condition="!Exists('3rd\Fody.1.29.4\build\portable-net+sl+win+wpa+wp\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '3rd\Fody.1.29.4\build\portable-net+sl+win+wpa+wp\Fody.targets'))" /> | |||
</Target> | |||
<Import Project="3rd\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets" Condition="Exists('3rd\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" /> | |||
<Import Project="3rd\Fody.1.29.4\build\portable-net+sl+win+wpa+wp\Fody.targets" Condition="Exists('3rd\Fody.1.29.4\build\portable-net+sl+win+wpa+wp\Fody.targets')" /> | |||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. | |||
Other similar extension points exist, see Microsoft.Common.targets. | |||
<Target Name="BeforeBuild"> | |||
@@ -13,19 +13,34 @@ 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); | |||
} | |||
[TestMethod] | |||
public void TestMD5() | |||
{ | |||
for (int len = 1; len < 64; len++) | |||
{ | |||
System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create(); | |||
byte[] bytes = new byte[len]; | |||
var random = new Random(); | |||
random.NextBytes(bytes); | |||
string md5str = Convert.ToBase64String(md5.ComputeHash(bytes)); | |||
string md5str2 = Convert.ToBase64String(MbedTLS.MD5(bytes)); | |||
Assert.IsTrue(md5str == md5str2); | |||
} | |||
} | |||
private void RunEncryptionRound(IEncryptor encryptor, IEncryptor decryptor) | |||
{ | |||
byte[] plain = new byte[16384]; | |||
byte[] cipher = new byte[plain.Length + 16]; | |||
byte[] cipher = new byte[plain.Length + 16 + IVEncryptor.ONETIMEAUTH_BYTES + IVEncryptor.AUTH_BYTES]; | |||
byte[] plain2 = new byte[plain.Length + 16]; | |||
int outLen = 0; | |||
int outLen2 = 0; | |||
@@ -84,8 +99,8 @@ namespace test | |||
{ | |||
IEncryptor encryptor; | |||
IEncryptor decryptor; | |||
encryptor = new PolarSSLEncryptor("aes-256-cfb", "barfoo!"); | |||
decryptor = new PolarSSLEncryptor("aes-256-cfb", "barfoo!"); | |||
encryptor = new PolarSSLEncryptor("aes-256-cfb", "barfoo!", false, false); | |||
decryptor = new PolarSSLEncryptor("aes-256-cfb", "barfoo!", false, false); | |||
RunEncryptionRound(encryptor, decryptor); | |||
} | |||
} | |||
@@ -124,8 +139,8 @@ namespace test | |||
var random = new Random(); | |||
IEncryptor encryptor; | |||
IEncryptor decryptor; | |||
encryptor = new PolarSSLEncryptor("rc4-md5", "barfoo!"); | |||
decryptor = new PolarSSLEncryptor("rc4-md5", "barfoo!"); | |||
encryptor = new PolarSSLEncryptor("rc4-md5", "barfoo!", false, false); | |||
decryptor = new PolarSSLEncryptor("rc4-md5", "barfoo!", false, false); | |||
RunEncryptionRound(encryptor, decryptor); | |||
} | |||
} | |||
@@ -164,8 +179,8 @@ namespace test | |||
var random = new Random(); | |||
IEncryptor encryptor; | |||
IEncryptor decryptor; | |||
encryptor = new SodiumEncryptor("salsa20", "barfoo!"); | |||
decryptor = new SodiumEncryptor("salsa20", "barfoo!"); | |||
encryptor = new SodiumEncryptor("salsa20", "barfoo!", false, false); | |||
decryptor = new SodiumEncryptor("salsa20", "barfoo!", false, false); | |||
RunEncryptionRound(encryptor, decryptor); | |||
} | |||
} | |||
@@ -0,0 +1,8 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<packages> | |||
<package id="Microsoft.Bcl" version="1.1.10" targetFramework="net45" /> | |||
<package id="Microsoft.Bcl.Async" version="1.0.168" targetFramework="net45" /> | |||
<package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net45" /> | |||
<package id="Microsoft.Net.Http" version="2.2.29" targetFramework="net45" /> | |||
<package id="System.Net.Http" version="4.0.0" targetFramework="net45" /> | |||
</packages> |
@@ -17,6 +17,8 @@ | |||
<IsCodedUITest>False</IsCodedUITest> | |||
<TestProjectType>UnitTest</TestProjectType> | |||
<TargetFrameworkProfile /> | |||
<NuGetPackageImportStamp> | |||
</NuGetPackageImportStamp> | |||
</PropertyGroup> | |||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'"> | |||
<OutputPath>bin\x86\Debug\</OutputPath> | |||
@@ -30,7 +32,30 @@ | |||
<StartupObject /> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<Reference Include="Microsoft.Threading.Tasks, Version=1.0.12.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> | |||
<HintPath>..\shadowsocks-csharp\3rd\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll</HintPath> | |||
<Private>True</Private> | |||
</Reference> | |||
<Reference Include="Microsoft.Threading.Tasks.Extensions, Version=1.0.12.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> | |||
<HintPath>..\shadowsocks-csharp\3rd\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll</HintPath> | |||
<Private>True</Private> | |||
</Reference> | |||
<Reference Include="Microsoft.Threading.Tasks.Extensions.Desktop, Version=1.0.168.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> | |||
<HintPath>..\shadowsocks-csharp\3rd\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll</HintPath> | |||
<Private>True</Private> | |||
</Reference> | |||
<Reference Include="System" /> | |||
<Reference Include="System.Net" /> | |||
<Reference Include="System.Net.Http" /> | |||
<Reference Include="System.Net.Http.Extensions, Version=2.2.29.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> | |||
<HintPath>..\shadowsocks-csharp\3rd\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Extensions.dll</HintPath> | |||
<Private>True</Private> | |||
</Reference> | |||
<Reference Include="System.Net.Http.Primitives, Version=4.2.29.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> | |||
<HintPath>..\shadowsocks-csharp\3rd\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Primitives.dll</HintPath> | |||
<Private>True</Private> | |||
</Reference> | |||
<Reference Include="System.Net.Http.WebRequest" /> | |||
</ItemGroup> | |||
<Choose> | |||
<When Condition="('$(VisualStudioVersion)' == '10.0' or '$(VisualStudioVersion)' == '') and '$(TargetFrameworkVersion)' == 'v3.5'"> | |||
@@ -54,6 +79,9 @@ | |||
<Name>shadowsocks-csharp</Name> | |||
</ProjectReference> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<None Include="packages.config" /> | |||
</ItemGroup> | |||
<Choose> | |||
<When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'"> | |||
<ItemGroup> | |||
@@ -74,6 +102,13 @@ | |||
</Choose> | |||
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" /> | |||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | |||
<Import Project="..\shadowsocks-csharp\3rd\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets" Condition="Exists('..\shadowsocks-csharp\3rd\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" /> | |||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild"> | |||
<PropertyGroup> | |||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText> | |||
</PropertyGroup> | |||
<Error Condition="!Exists('..\shadowsocks-csharp\3rd\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\shadowsocks-csharp\3rd\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets'))" /> | |||
</Target> | |||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. | |||
Other similar extension points exist, see Microsoft.Common.targets. | |||
<Target Name="BeforeBuild"> | |||