Feature/statistics uitags/3.0
@@ -5,3 +5,9 @@ shadowsocks-csharp/shadowsocks-csharp.csproj.user | |||||
TestResults | TestResults | ||||
*.suo | *.suo | ||||
shadowsocks-csharp/3rd/* | |||||
!shadowsocks-csharp/3rd/zxing/ | |||||
!shadowsocks-csharp/3rd/SimpleJson.cs | |||||
packages/* | |||||
shadowsocks-csharp.sln.DotSettings.user |
@@ -0,0 +1,5 @@ | |||||
<configuration> | |||||
<config> | |||||
<add key="repositoryPath" value="shadowsocks-csharp\3rd" /> | |||||
</config> | |||||
</configuration> |
@@ -1,50 +1,72 @@ | |||||
using System; | using System; | ||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Globalization; | |||||
using System.IO; | using System.IO; | ||||
using System.Linq; | using System.Linq; | ||||
using System.Net; | |||||
using System.Net.Http; | |||||
using System.Net.NetworkInformation; | using System.Net.NetworkInformation; | ||||
using System.Net.Sockets; | |||||
using System.Threading; | using System.Threading; | ||||
using System.Threading.Tasks; | |||||
using Shadowsocks.Model; | using Shadowsocks.Model; | ||||
using System.Reflection; | |||||
using Shadowsocks.Util; | using Shadowsocks.Util; | ||||
namespace Shadowsocks.Controller | 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; | public static string AvailabilityStatisticsFile; | ||||
//static constructor to initialize every public static fields before refereced | //static constructor to initialize every public static fields before refereced | ||||
static AvailabilityStatistics() | static AvailabilityStatistics() | ||||
{ | { | ||||
string temppath = Utils.GetTempPath(); | |||||
var temppath = Utils.GetTempPath(); | |||||
AvailabilityStatisticsFile = Path.Combine(temppath, StatisticsFilesName); | AvailabilityStatisticsFile = Path.Combine(temppath, StatisticsFilesName); | ||||
} | } | ||||
public bool Set(bool enabled) | |||||
public AvailabilityStatistics(Configuration config, StatisticsStrategyConfiguration statisticsConfig) | |||||
{ | |||||
UpdateConfiguration(config, statisticsConfig); | |||||
} | |||||
public bool Set(StatisticsStrategyConfiguration config) | |||||
{ | { | ||||
_config = config; | |||||
try | 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 | else | ||||
{ | { | ||||
timer?.Dispose(); | |||||
_timer?.Dispose(); | |||||
} | } | ||||
return true; | return true; | ||||
} | } | ||||
@@ -55,64 +77,240 @@ 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 | |||||
{ | { | ||||
Logging.Debug("eveluating " + server.FriendlyName()); | |||||
foreach (var _ in Enumerable.Range(0, Repeat)) | |||||
jsonString = await new HttpClient().GetStringAsync(API); | |||||
} | |||||
catch (HttpRequestException e) | |||||
{ | |||||
Logging.LogUsefulException(e); | |||||
return null; | |||||
} | |||||
dynamic obj; | |||||
if (!SimpleJson.SimpleJson.TryDeserializeObject(jsonString, out obj)) return null; | |||||
string country = obj["country"]; | |||||
string city = obj["city"]; | |||||
string isp = 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 IP = Dns.GetHostAddresses(server.server).First(ip => ip.AddressFamily == AddressFamily.InterNetwork); | |||||
var ping = new Ping(); | |||||
var ret = new List<DataList>(); | |||||
foreach ( | |||||
var timestamp in Enumerable.Range(0, Repeat).Select(_ => DateTime.Now.ToString(DateTimePattern))) | |||||
{ | |||||
//ICMP echo. we can also set options and special bytes | |||||
try | |||||
{ | { | ||||
//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); | |||||
} | |||||
catch (Exception e) | |||||
var reply = await ping.SendTaskAsync(IP, Timeout); | |||||
ret.Add(new List<KeyValuePair<string, string>> | |||||
{ | { | ||||
Console.WriteLine($"An exception occured when eveluating {server.FriendlyName()}"); | |||||
Logging.LogUsefulException(e); | |||||
} | |||||
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) | |||||
{ | |||||
Console.WriteLine($"An exception occured when eveluating {server.FriendlyName()}"); | |||||
Logging.LogUsefulException(e); | |||||
} | } | ||||
} | } | ||||
return ret; | |||||
} | |||||
private void Run(object obj) | |||||
{ | |||||
LoadRawStatistics(); | |||||
FilterRawStatistics(); | |||||
evaluate(); | |||||
} | } | ||||
private static void Append(List<KeyValuePair<string, string>> data) | |||||
private async void evaluate() | |||||
{ | { | ||||
string dataLine = string.Join(Delimiter, data.Select(kv => kv.Value).ToArray()); | |||||
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(DataList dataList, IEnumerable<DataUnit> extra) | |||||
{ | |||||
var data = dataList.Concat(extra); | |||||
var dataLine = string.Join(Delimiter, data.Select(kv => kv.Value).ToArray()); | |||||
string[] lines; | string[] lines; | ||||
if (!File.Exists(AvailabilityStatisticsFile)) | 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 | else | ||||
{ | { | ||||
lines = new string[] { dataLine }; | |||||
lines = new[] {dataLine}; | |||||
} | |||||
try | |||||
{ | |||||
File.AppendAllLines(AvailabilityStatisticsFile, lines); | |||||
} | |||||
catch (IOException e) | |||||
{ | |||||
Logging.LogUsefulException(e); | |||||
} | } | ||||
File.AppendAllLines(AvailabilityStatisticsFile, lines); | |||||
} | } | ||||
internal void UpdateConfiguration(Configuration _config) | |||||
internal void UpdateConfiguration(Configuration config, StatisticsStrategyConfiguration statisticsConfig) | |||||
{ | { | ||||
Set(_config.availabilityStatistics); | |||||
servers = _config.configs; | |||||
Set(statisticsConfig); | |||||
_servers = config.configs; | |||||
} | } | ||||
private class State | |||||
private async void FilterRawStatistics() | |||||
{ | { | ||||
public List<KeyValuePair<string, string>> data = new List<KeyValuePair<string, string>>(); | |||||
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)) | |||||
{ | |||||
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"; | |||||
} | |||||
public class RawStatisticsData | |||||
{ | |||||
public DateTime Timestamp; | |||||
public string ServerName; | |||||
public string ICMPStatus; | |||||
public int RoundtripTime; | |||||
public string Geolocation; | |||||
public string ISP ; | |||||
} | |||||
public class StatisticsData | |||||
{ | |||||
public float PackageLoss; | |||||
public int AverageResponse; | |||||
public int MinResponse; | |||||
public int MaxResponse; | |||||
} | |||||
} | } | ||||
} | } |
@@ -25,7 +25,9 @@ namespace Shadowsocks.Controller | |||||
private StrategyManager _strategyManager; | private StrategyManager _strategyManager; | ||||
private PolipoRunner polipoRunner; | private PolipoRunner polipoRunner; | ||||
private GFWListUpdater gfwListUpdater; | private GFWListUpdater gfwListUpdater; | ||||
private AvailabilityStatistics _availabilityStatics; | |||||
public AvailabilityStatistics availabilityStatistics { get; private set; } | |||||
public StatisticsStrategyConfiguration StatisticsConfiguration { get; private set; } | |||||
private bool stopped = false; | private bool stopped = false; | ||||
private bool _systemProxyIsDirty = false; | private bool _systemProxyIsDirty = false; | ||||
@@ -53,10 +55,12 @@ namespace Shadowsocks.Controller | |||||
public ShadowsocksController() | public ShadowsocksController() | ||||
{ | { | ||||
_config = Configuration.Load(); | _config = Configuration.Load(); | ||||
StatisticsConfiguration = StatisticsStrategyConfiguration.Load(); | |||||
_strategyManager = new StrategyManager(this); | _strategyManager = new StrategyManager(this); | ||||
StartReleasingMemory(); | StartReleasingMemory(); | ||||
} | } | ||||
public void Start() | public void Start() | ||||
{ | { | ||||
Reload(); | Reload(); | ||||
@@ -125,6 +129,12 @@ namespace Shadowsocks.Controller | |||||
Configuration.Save(_config); | Configuration.Save(_config); | ||||
} | } | ||||
public void SaveStrategyConfigurations(StatisticsStrategyConfiguration configuration) | |||||
{ | |||||
StatisticsConfiguration = configuration; | |||||
StatisticsStrategyConfiguration.Save(configuration); | |||||
} | |||||
public bool AddServerBySSURL(string ssURL) | public bool AddServerBySSURL(string ssURL) | ||||
{ | { | ||||
try | try | ||||
@@ -248,14 +258,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) | public void SavePACUrl(string pacUrl) | ||||
@@ -296,6 +304,7 @@ namespace Shadowsocks.Controller | |||||
{ | { | ||||
// some logic in configuration updated the config when saving, we need to read it again | // some logic in configuration updated the config when saving, we need to read it again | ||||
_config = Configuration.Load(); | _config = Configuration.Load(); | ||||
StatisticsConfiguration = StatisticsStrategyConfiguration.Load(); | |||||
if (polipoRunner == null) | if (polipoRunner == null) | ||||
{ | { | ||||
@@ -314,17 +323,16 @@ namespace Shadowsocks.Controller | |||||
gfwListUpdater.Error += pacServer_PACUpdateError; | 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() | // 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 | // 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 | // though UseShellExecute is set to true now | ||||
@@ -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,152 @@ | |||||
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 = new List<IStrategy>(); | ||||
_strategies.Add(new BalancingStrategy(controller)); | _strategies.Add(new BalancingStrategy(controller)); | ||||
_strategies.Add(new HighAvailabilityStrategy(controller)); | _strategies.Add(new HighAvailabilityStrategy(controller)); | ||||
_strategies.Add(new SimplyChooseByStatisticsStrategy(controller)); | |||||
_strategies.Add(new StatisticsStrategy(controller)); | |||||
// TODO: load DLL plugins | // TODO: load DLL plugins | ||||
} | } | ||||
public IList<IStrategy> GetStrategies() | public IList<IStrategy> GetStrategies() | ||||
@@ -0,0 +1,5 @@ | |||||
<?xml version="1.0" encoding="utf-8"?> | |||||
<Weavers> | |||||
<Costura/> | |||||
</Weavers> |
@@ -4,6 +4,7 @@ using System.Collections.Generic; | |||||
using System.IO; | using System.IO; | ||||
using System.Text; | using System.Text; | ||||
using System.Windows.Forms; | using System.Windows.Forms; | ||||
using SimpleJson; | |||||
namespace Shadowsocks.Model | namespace Shadowsocks.Model | ||||
{ | { | ||||
@@ -0,0 +1,107 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.IO; | |||||
using System.Linq; | |||||
using System.Reflection; | |||||
using Shadowsocks.Controller; | |||||
using Shadowsocks.Controller.Strategy; | |||||
using SimpleJson; | |||||
using Newtonsoft.Json; | |||||
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 e) | |||||
{ | |||||
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; } | |||||
} | |||||
} | |||||
} |
@@ -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> |
@@ -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> |
@@ -30,7 +30,6 @@ namespace Shadowsocks.View | |||||
private MenuItem enableItem; | private MenuItem enableItem; | ||||
private MenuItem modeItem; | private MenuItem modeItem; | ||||
private MenuItem AutoStartupItem; | private MenuItem AutoStartupItem; | ||||
private MenuItem AvailabilityStatistics; | |||||
private MenuItem ShareOverLANItem; | private MenuItem ShareOverLANItem; | ||||
private MenuItem SeperatorItem; | private MenuItem SeperatorItem; | ||||
private MenuItem ConfigItem; | private MenuItem ConfigItem; | ||||
@@ -172,6 +171,7 @@ namespace Shadowsocks.View | |||||
this.ServersItem = CreateMenuGroup("Servers", new MenuItem[] { | this.ServersItem = CreateMenuGroup("Servers", new MenuItem[] { | ||||
this.SeperatorItem = new MenuItem("-"), | this.SeperatorItem = new MenuItem("-"), | ||||
this.ConfigItem = CreateMenuItem("Edit Servers...", new EventHandler(this.Config_Click)), | this.ConfigItem = CreateMenuItem("Edit Servers...", new EventHandler(this.Config_Click)), | ||||
CreateMenuItem("Statistics Config...", StatisticsConfigItem_Click), | |||||
CreateMenuItem("Show QRCode...", new EventHandler(this.QRCodeItem_Click)), | CreateMenuItem("Show QRCode...", new EventHandler(this.QRCodeItem_Click)), | ||||
CreateMenuItem("Scan QRCode from Screen...", new EventHandler(this.ScanQRCodeItem_Click)) | CreateMenuItem("Scan QRCode from Screen...", new EventHandler(this.ScanQRCodeItem_Click)) | ||||
}), | }), | ||||
@@ -186,7 +186,6 @@ namespace Shadowsocks.View | |||||
}), | }), | ||||
new MenuItem("-"), | new MenuItem("-"), | ||||
this.AutoStartupItem = CreateMenuItem("Start on Boot", new EventHandler(this.AutoStartupItem_Click)), | 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)), | this.ShareOverLANItem = CreateMenuItem("Allow Clients from LAN", new EventHandler(this.ShareOverLANItem_Click)), | ||||
new MenuItem("-"), | new MenuItem("-"), | ||||
CreateMenuItem("Show Logs...", new EventHandler(this.ShowLogItem_Click)), | CreateMenuItem("Show Logs...", new EventHandler(this.ShowLogItem_Click)), | ||||
@@ -201,6 +200,7 @@ namespace Shadowsocks.View | |||||
}); | }); | ||||
} | } | ||||
private void controller_ConfigChanged(object sender, EventArgs e) | private void controller_ConfigChanged(object sender, EventArgs e) | ||||
{ | { | ||||
LoadCurrentConfiguration(); | LoadCurrentConfiguration(); | ||||
@@ -285,7 +285,6 @@ namespace Shadowsocks.View | |||||
PACModeItem.Checked = !config.global; | PACModeItem.Checked = !config.global; | ||||
ShareOverLANItem.Checked = config.shareOverLan; | ShareOverLANItem.Checked = config.shareOverLan; | ||||
AutoStartupItem.Checked = AutoStartup.Check(); | AutoStartupItem.Checked = AutoStartup.Check(); | ||||
AvailabilityStatistics.Checked = config.availabilityStatistics; | |||||
onlinePACItem.Checked = onlinePACItem.Enabled && config.useOnlinePac; | onlinePACItem.Checked = onlinePACItem.Enabled && config.useOnlinePac; | ||||
localPACItem.Checked = !onlinePACItem.Checked; | localPACItem.Checked = !onlinePACItem.Checked; | ||||
UpdatePACItemsEnabledStatus(); | UpdatePACItemsEnabledStatus(); | ||||
@@ -441,6 +440,12 @@ namespace Shadowsocks.View | |||||
new LogForm(controller, argument).Show(); | new LogForm(controller, argument).Show(); | ||||
} | } | ||||
private void StatisticsConfigItem_Click(object sender, EventArgs e) | |||||
{ | |||||
StatisticsStrategyConfigurationForm form = new StatisticsStrategyConfigurationForm(controller); | |||||
form.Show(); | |||||
} | |||||
private void QRCodeItem_Click(object sender, EventArgs e) | private void QRCodeItem_Click(object sender, EventArgs e) | ||||
{ | { | ||||
@@ -551,11 +556,6 @@ namespace Shadowsocks.View | |||||
} | } | ||||
} | } | ||||
private void AvailabilityStatisticsItem_Click(object sender, EventArgs e) { | |||||
AvailabilityStatistics.Checked = !AvailabilityStatistics.Checked; | |||||
controller.ToggleAvailabilityStatistics(AvailabilityStatistics.Checked); | |||||
} | |||||
private void LocalPACItem_Click(object sender, EventArgs e) | private void LocalPACItem_Click(object sender, EventArgs e) | ||||
{ | { | ||||
if (!localPACItem.Checked) | if (!localPACItem.Checked) | ||||
@@ -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,140 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Data; | |||||
using System.Linq; | |||||
using System.Windows.Forms; | |||||
using System.Windows.Forms.DataVisualization.Charting; | |||||
using Shadowsocks.Controller; | |||||
using Shadowsocks.Model; | |||||
using SimpleJson; | |||||
using System.Net.NetworkInformation; | |||||
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(); | |||||
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> | <configuration> | ||||
<startup> | <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.8.0" newVersion="2.6.8.0" /> | |||||
</dependentAssembly> | |||||
<dependentAssembly> | |||||
<assemblyIdentity name="System.Threading.Tasks" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" /> | |||||
<bindingRedirect oldVersion="0.0.0.0-2.6.8.0" newVersion="2.6.8.0" /> | |||||
</dependentAssembly> | |||||
</assemblyBinding> | |||||
</runtime> | |||||
</configuration> |
@@ -10,7 +10,7 @@ | |||||
</trustInfo> | </trustInfo> | ||||
<asmv3:application> | <asmv3:application> | ||||
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings"> | <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings"> | ||||
<dpiAware>true</dpiAware> | |||||
<dpiAware>True/PM</dpiAware> | |||||
</asmv3:windowsSettings> | </asmv3:windowsSettings> | ||||
</asmv3:application> | </asmv3:application> | ||||
</assembly> | </assembly> |
@@ -0,0 +1,9 @@ | |||||
<?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.3" targetFramework="net4-client" developmentDependency="true" /> | |||||
<package id="Microsoft.Bcl" version="1.1.8" targetFramework="net4-client" /> | |||||
<package id="Microsoft.Bcl.Async" version="1.0.168" targetFramework="net4-client" /> | |||||
<package id="Microsoft.Bcl.Build" version="1.0.14" targetFramework="net4-client" /> | |||||
<package id="Newtonsoft.Json" version="7.0.1" targetFramework="net4-client" /> | |||||
</packages> |
@@ -36,6 +36,8 @@ | |||||
<ApplicationVersion>1.0.0.%2a</ApplicationVersion> | <ApplicationVersion>1.0.0.%2a</ApplicationVersion> | ||||
<UseApplicationTrust>false</UseApplicationTrust> | <UseApplicationTrust>false</UseApplicationTrust> | ||||
<BootstrapperEnabled>true</BootstrapperEnabled> | <BootstrapperEnabled>true</BootstrapperEnabled> | ||||
<NuGetPackageImportStamp> | |||||
</NuGetPackageImportStamp> | |||||
</PropertyGroup> | </PropertyGroup> | ||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'"> | <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'"> | ||||
<DebugSymbols>true</DebugSymbols> | <DebugSymbols>true</DebugSymbols> | ||||
@@ -62,12 +64,56 @@ | |||||
<ApplicationManifest>app.manifest</ApplicationManifest> | <ApplicationManifest>app.manifest</ApplicationManifest> | ||||
</PropertyGroup> | </PropertyGroup> | ||||
<ItemGroup> | <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> | |||||
<EmbedInteropTypes>False</EmbedInteropTypes> | |||||
</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> | |||||
<EmbedInteropTypes>False</EmbedInteropTypes> | |||||
</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> | |||||
<EmbedInteropTypes>False</EmbedInteropTypes> | |||||
</Reference> | |||||
<Reference Include="Microsoft.VisualBasic" /> | <Reference Include="Microsoft.VisualBasic" /> | ||||
<Reference Include="Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL"> | |||||
<HintPath>3rd\Newtonsoft.Json.7.0.1\lib\net40\Newtonsoft.Json.dll</HintPath> | |||||
<Private>True</Private> | |||||
</Reference> | |||||
<Reference Include="PresentationCore" /> | |||||
<Reference Include="PresentationFramework" /> | |||||
<Reference Include="System" /> | <Reference Include="System" /> | ||||
<Reference Include="System.Data" /> | <Reference Include="System.Data" /> | ||||
<Reference Include="System.Drawing" /> | <Reference Include="System.Drawing" /> | ||||
<Reference Include="System.IO, Version=2.6.8.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> | |||||
<HintPath>3rd\Microsoft.Bcl.1.1.8\lib\net40\System.IO.dll</HintPath> | |||||
<Private>True</Private> | |||||
<EmbedInteropTypes>False</EmbedInteropTypes> | |||||
</Reference> | |||||
<Reference Include="System.Net" /> | |||||
<Reference Include="System.Net.Http, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> | |||||
<Reference Include="System.Runtime, Version=2.6.8.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> | |||||
<HintPath>3rd\Microsoft.Bcl.1.1.8\lib\net40\System.Runtime.dll</HintPath> | |||||
<Private>True</Private> | |||||
<EmbedInteropTypes>False</EmbedInteropTypes> | |||||
</Reference> | |||||
<Reference Include="System.Threading.Tasks, Version=2.6.8.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> | |||||
<HintPath>3rd\Microsoft.Bcl.1.1.8\lib\net40\System.Threading.Tasks.dll</HintPath> | |||||
<Private>True</Private> | |||||
<EmbedInteropTypes>False</EmbedInteropTypes> | |||||
</Reference> | |||||
<Reference Include="System.Windows.Forms" /> | <Reference Include="System.Windows.Forms" /> | ||||
<Reference Include="System.Windows.Forms.DataVisualization" /> | |||||
<Reference Include="System.Xaml" /> | |||||
<Reference Include="System.XML" /> | <Reference Include="System.XML" /> | ||||
<Reference Include="UIAutomationProvider" /> | |||||
<Reference Include="WindowsBase" /> | |||||
<Reference Include="WindowsFormsIntegration" /> | |||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<Compile Include="3rd\zxing\BarcodeFormat.cs" /> | <Compile Include="3rd\zxing\BarcodeFormat.cs" /> | ||||
@@ -125,7 +171,7 @@ | |||||
<Compile Include="3rd\zxing\WriterException.cs" /> | <Compile Include="3rd\zxing\WriterException.cs" /> | ||||
<Compile Include="Controller\Service\AvailabilityStatistics.cs" /> | <Compile Include="Controller\Service\AvailabilityStatistics.cs" /> | ||||
<Compile Include="Controller\Strategy\HighAvailabilityStrategy.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\System\AutoStartup.cs" /> | ||||
<Compile Include="Controller\FileManager.cs" /> | <Compile Include="Controller\FileManager.cs" /> | ||||
<Compile Include="Controller\Service\GFWListUpdater.cs" /> | <Compile Include="Controller\Service\GFWListUpdater.cs" /> | ||||
@@ -148,6 +194,7 @@ | |||||
<Compile Include="Model\LogViewerConfig.cs" /> | <Compile Include="Model\LogViewerConfig.cs" /> | ||||
<Compile Include="Model\Server.cs" /> | <Compile Include="Model\Server.cs" /> | ||||
<Compile Include="Model\Configuration.cs" /> | <Compile Include="Model\Configuration.cs" /> | ||||
<Compile Include="Model\StatisticsStrategyConfiguration.cs" /> | |||||
<Compile Include="Properties\Resources.Designer.cs"> | <Compile Include="Properties\Resources.Designer.cs"> | ||||
<AutoGen>True</AutoGen> | <AutoGen>True</AutoGen> | ||||
<DesignTime>True</DesignTime> | <DesignTime>True</DesignTime> | ||||
@@ -169,6 +216,12 @@ | |||||
<Compile Include="Properties\AssemblyInfo.cs" /> | <Compile Include="Properties\AssemblyInfo.cs" /> | ||||
<Compile Include="Controller\ShadowsocksController.cs" /> | <Compile Include="Controller\ShadowsocksController.cs" /> | ||||
<Compile Include="Controller\System\SystemProxy.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"> | <Compile Include="View\LogForm.cs"> | ||||
<SubType>Form</SubType> | <SubType>Form</SubType> | ||||
</Compile> | </Compile> | ||||
@@ -185,6 +238,12 @@ | |||||
<Compile Include="View\QRCodeSplashForm.cs"> | <Compile Include="View\QRCodeSplashForm.cs"> | ||||
<SubType>Form</SubType> | <SubType>Form</SubType> | ||||
</Compile> | </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"> | <EmbeddedResource Include="View\ConfigForm.resx"> | ||||
<DependentUpon>ConfigForm.cs</DependentUpon> | <DependentUpon>ConfigForm.cs</DependentUpon> | ||||
<SubType>Designer</SubType> | <SubType>Designer</SubType> | ||||
@@ -194,12 +253,18 @@ | |||||
<SubType>Designer</SubType> | <SubType>Designer</SubType> | ||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput> | <LastGenOutput>Resources.Designer.cs</LastGenOutput> | ||||
</EmbeddedResource> | </EmbeddedResource> | ||||
<EmbeddedResource Include="View\CalculationControl.resx"> | |||||
<DependentUpon>CalculationControl.cs</DependentUpon> | |||||
</EmbeddedResource> | |||||
<EmbeddedResource Include="View\LogForm.resx"> | <EmbeddedResource Include="View\LogForm.resx"> | ||||
<DependentUpon>LogForm.cs</DependentUpon> | <DependentUpon>LogForm.cs</DependentUpon> | ||||
</EmbeddedResource> | </EmbeddedResource> | ||||
<EmbeddedResource Include="View\QRCodeForm.resx"> | <EmbeddedResource Include="View\QRCodeForm.resx"> | ||||
<DependentUpon>QRCodeForm.cs</DependentUpon> | <DependentUpon>QRCodeForm.cs</DependentUpon> | ||||
</EmbeddedResource> | </EmbeddedResource> | ||||
<EmbeddedResource Include="View\StatisticsStrategyConfigurationForm.resx"> | |||||
<DependentUpon>StatisticsStrategyConfigurationForm.cs</DependentUpon> | |||||
</EmbeddedResource> | |||||
<None Include="app.config" /> | <None Include="app.config" /> | ||||
<None Include="app.manifest"> | <None Include="app.manifest"> | ||||
<SubType>Designer</SubType> | <SubType>Designer</SubType> | ||||
@@ -211,6 +276,8 @@ | |||||
<None Include="Data\proxy.pac.txt.gz" /> | <None Include="Data\proxy.pac.txt.gz" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
<None Include="packages.config" /> | |||||
<None Include="Properties\DataSources\Shadowsocks.Model.StatisticsStrategyConfiguration.datasource" /> | |||||
<None Include="Resources\ss20.png" /> | <None Include="Resources\ss20.png" /> | ||||
<None Include="Resources\ss16.png" /> | <None Include="Resources\ss16.png" /> | ||||
<None Include="Resources\ss24.png" /> | <None Include="Resources\ss24.png" /> | ||||
@@ -218,6 +285,9 @@ | |||||
<Content Include="Data\cn.txt" /> | <Content Include="Data\cn.txt" /> | ||||
<Content Include="Data\privoxy_conf.txt" /> | <Content Include="Data\privoxy_conf.txt" /> | ||||
<Content Include="Data\user-rule.txt" /> | <Content Include="Data\user-rule.txt" /> | ||||
<Content Include="FodyWeavers.xml"> | |||||
<SubType>Designer</SubType> | |||||
</Content> | |||||
<Content Include="shadowsocks.ico" /> | <Content Include="shadowsocks.ico" /> | ||||
</ItemGroup> | </ItemGroup> | ||||
<ItemGroup> | <ItemGroup> | ||||
@@ -253,6 +323,18 @@ | |||||
</BootstrapperPackage> | </BootstrapperPackage> | ||||
</ItemGroup> | </ItemGroup> | ||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> | ||||
<Import Project="3rd\Microsoft.Bcl.Build.1.0.14\tools\Microsoft.Bcl.Build.targets" Condition="Exists('3rd\Microsoft.Bcl.Build.1.0.14\tools\Microsoft.Bcl.Build.targets')" /> | |||||
<Target Name="EnsureBclBuildImported" BeforeTargets="BeforeBuild" Condition="'$(BclBuildImported)' == ''"> | |||||
<Error Condition="!Exists('3rd\Microsoft.Bcl.Build.1.0.14\tools\Microsoft.Bcl.Build.targets')" Text="This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=317567." HelpKeyword="BCLBUILD2001" /> | |||||
<Error Condition="Exists('3rd\Microsoft.Bcl.Build.1.0.14\tools\Microsoft.Bcl.Build.targets')" Text="The build restored NuGet packages. Build the project again to include these packages in the build. For more information, see http://go.microsoft.com/fwlink/?LinkID=317568." HelpKeyword="BCLBUILD2002" /> | |||||
</Target> | |||||
<Import Project="3rd\Fody.1.29.3\build\portable-net+sl+win+wpa+wp\Fody.targets" Condition="Exists('3rd\Fody.1.29.3\build\portable-net+sl+win+wpa+wp\Fody.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\Fody.1.29.3\build\portable-net+sl+win+wpa+wp\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '3rd\Fody.1.29.3\build\portable-net+sl+win+wpa+wp\Fody.targets'))" /> | |||||
</Target> | |||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. | <!-- 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. | Other similar extension points exist, see Microsoft.Common.targets. | ||||
<Target Name="BeforeBuild"> | <Target Name="BeforeBuild"> | ||||