Feature/statistics uitags/3.0
@@ -5,3 +5,9 @@ shadowsocks-csharp/shadowsocks-csharp.csproj.user | |||
TestResults | |||
*.suo | |||
shadowsocks-csharp/3rd/* | |||
!shadowsocks-csharp/3rd/zxing/ | |||
!shadowsocks-csharp/3rd/SimpleJson.cs | |||
packages/* | |||
shadowsocks-csharp.sln.DotSettings.user |
@@ -0,0 +1,5 @@ | |||
<configuration> | |||
<config> | |||
<add key="repositoryPath" value="shadowsocks-csharp\3rd" /> | |||
</config> | |||
</configuration> |
@@ -1,50 +1,72 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Globalization; | |||
using System.IO; | |||
using System.Linq; | |||
using System.Net; | |||
using System.Net.Http; | |||
using System.Net.NetworkInformation; | |||
using System.Net.Sockets; | |||
using System.Threading; | |||
using System.Threading.Tasks; | |||
using Shadowsocks.Model; | |||
using System.Reflection; | |||
using Shadowsocks.Util; | |||
namespace Shadowsocks.Controller | |||
{ | |||
class AvailabilityStatistics | |||
using DataUnit = KeyValuePair<string, string>; | |||
using DataList = List<KeyValuePair<string, string>>; | |||
using Statistics = Dictionary<string, List<AvailabilityStatistics.RawStatisticsData>>; | |||
public class AvailabilityStatistics | |||
{ | |||
private static readonly string StatisticsFilesName = "shadowsocks.availability.csv"; | |||
private static readonly string Delimiter = ","; | |||
private static readonly int Timeout = 500; | |||
private static readonly int Repeat = 4; //repeat times every evaluation | |||
private static readonly int Interval = 10 * 60 * 1000; //evaluate proxies every 15 minutes | |||
private Timer timer = null; | |||
private State state = null; | |||
private List<Server> servers; | |||
public static readonly string DateTimePattern = "yyyy-MM-dd HH:mm:ss"; | |||
private const string StatisticsFilesName = "shadowsocks.availability.csv"; | |||
private const string Delimiter = ","; | |||
private const int Timeout = 500; | |||
private const int DelayBeforeStart = 1000; | |||
public Statistics RawStatistics { get; private set; } | |||
public Statistics FilteredStatistics { get; private set; } | |||
public static readonly DateTime UnknownDateTime = new DateTime(1970, 1, 1); | |||
private int Repeat => _config.RepeatTimesNum; | |||
private const int RetryInterval = 2*60*1000; //retry 2 minutes after failed | |||
private int Interval => (int) TimeSpan.FromMinutes(_config.DataCollectionMinutes).TotalMilliseconds; | |||
private Timer _timer; | |||
private State _state; | |||
private List<Server> _servers; | |||
private StatisticsStrategyConfiguration _config; | |||
public static string AvailabilityStatisticsFile; | |||
//static constructor to initialize every public static fields before refereced | |||
static AvailabilityStatistics() | |||
{ | |||
string temppath = Utils.GetTempPath(); | |||
var temppath = Utils.GetTempPath(); | |||
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 | |||
{ | |||
if (enabled) | |||
if (config.StatisticsEnabled) | |||
{ | |||
if (timer?.Change(0, Interval) == null) | |||
if (_timer?.Change(DelayBeforeStart, Interval) == null) | |||
{ | |||
state = new State(); | |||
timer = new Timer(Evaluate, state, 0, Interval); | |||
_state = new State(); | |||
_timer = new Timer(Run, _state, DelayBeforeStart, Interval); | |||
} | |||
} | |||
else | |||
{ | |||
timer?.Dispose(); | |||
_timer?.Dispose(); | |||
} | |||
return true; | |||
} | |||
@@ -55,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; | |||
if (!File.Exists(AvailabilityStatisticsFile)) | |||
{ | |||
string headerLine = string.Join(Delimiter, data.Select(kv => kv.Key).ToArray()); | |||
lines = new string[] { headerLine, dataLine }; | |||
var headerLine = string.Join(Delimiter, data.Select(kv => kv.Key).ToArray()); | |||
lines = new[] {headerLine, dataLine}; | |||
} | |||
else | |||
{ | |||
lines = new string[] { dataLine }; | |||
lines = new[] {dataLine}; | |||
} | |||
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 PolipoRunner polipoRunner; | |||
private GFWListUpdater gfwListUpdater; | |||
private AvailabilityStatistics _availabilityStatics; | |||
public AvailabilityStatistics availabilityStatistics { get; private set; } | |||
public StatisticsStrategyConfiguration StatisticsConfiguration { get; private set; } | |||
private bool stopped = false; | |||
private bool _systemProxyIsDirty = false; | |||
@@ -53,10 +55,12 @@ namespace Shadowsocks.Controller | |||
public ShadowsocksController() | |||
{ | |||
_config = Configuration.Load(); | |||
StatisticsConfiguration = StatisticsStrategyConfiguration.Load(); | |||
_strategyManager = new StrategyManager(this); | |||
StartReleasingMemory(); | |||
} | |||
public void Start() | |||
{ | |||
Reload(); | |||
@@ -125,6 +129,12 @@ namespace Shadowsocks.Controller | |||
Configuration.Save(_config); | |||
} | |||
public void SaveStrategyConfigurations(StatisticsStrategyConfiguration configuration) | |||
{ | |||
StatisticsConfiguration = configuration; | |||
StatisticsStrategyConfiguration.Save(configuration); | |||
} | |||
public bool AddServerBySSURL(string ssURL) | |||
{ | |||
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) | |||
@@ -296,6 +304,7 @@ namespace Shadowsocks.Controller | |||
{ | |||
// some logic in configuration updated the config when saving, we need to read it again | |||
_config = Configuration.Load(); | |||
StatisticsConfiguration = StatisticsStrategyConfiguration.Load(); | |||
if (polipoRunner == null) | |||
{ | |||
@@ -314,17 +323,16 @@ namespace Shadowsocks.Controller | |||
gfwListUpdater.Error += pacServer_PACUpdateError; | |||
} | |||
if (_listener != null) | |||
if (availabilityStatistics == null) | |||
{ | |||
_listener.Stop(); | |||
availabilityStatistics = new AvailabilityStatistics(_config, StatisticsConfiguration); | |||
} | |||
availabilityStatistics.UpdateConfiguration(_config, StatisticsConfiguration); | |||
if (_availabilityStatics == null) | |||
if (_listener != null) | |||
{ | |||
_availabilityStatics = new AvailabilityStatistics(); | |||
_availabilityStatics.UpdateConfiguration(_config); | |||
_listener.Stop(); | |||
} | |||
// don't put polipoRunner.Start() before pacServer.Stop() | |||
// or bind will fail when switching bind address from 0.0.0.0 to 127.0.0.1 | |||
// though UseShellExecute is set to true now | |||
@@ -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.Add(new BalancingStrategy(controller)); | |||
_strategies.Add(new HighAvailabilityStrategy(controller)); | |||
_strategies.Add(new SimplyChooseByStatisticsStrategy(controller)); | |||
_strategies.Add(new StatisticsStrategy(controller)); | |||
// TODO: load DLL plugins | |||
} | |||
public IList<IStrategy> GetStrategies() | |||
@@ -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.Text; | |||
using System.Windows.Forms; | |||
using SimpleJson; | |||
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 modeItem; | |||
private MenuItem AutoStartupItem; | |||
private MenuItem AvailabilityStatistics; | |||
private MenuItem ShareOverLANItem; | |||
private MenuItem SeperatorItem; | |||
private MenuItem ConfigItem; | |||
@@ -172,6 +171,7 @@ namespace Shadowsocks.View | |||
this.ServersItem = CreateMenuGroup("Servers", new MenuItem[] { | |||
this.SeperatorItem = new MenuItem("-"), | |||
this.ConfigItem = CreateMenuItem("Edit Servers...", new EventHandler(this.Config_Click)), | |||
CreateMenuItem("Statistics Config...", StatisticsConfigItem_Click), | |||
CreateMenuItem("Show QRCode...", new EventHandler(this.QRCodeItem_Click)), | |||
CreateMenuItem("Scan QRCode from Screen...", new EventHandler(this.ScanQRCodeItem_Click)) | |||
}), | |||
@@ -186,7 +186,6 @@ namespace Shadowsocks.View | |||
}), | |||
new MenuItem("-"), | |||
this.AutoStartupItem = CreateMenuItem("Start on Boot", new EventHandler(this.AutoStartupItem_Click)), | |||
this.AvailabilityStatistics = CreateMenuItem("Availability Statistics", new EventHandler(this.AvailabilityStatisticsItem_Click)), | |||
this.ShareOverLANItem = CreateMenuItem("Allow Clients from LAN", new EventHandler(this.ShareOverLANItem_Click)), | |||
new MenuItem("-"), | |||
CreateMenuItem("Show Logs...", new EventHandler(this.ShowLogItem_Click)), | |||
@@ -201,6 +200,7 @@ namespace Shadowsocks.View | |||
}); | |||
} | |||
private void controller_ConfigChanged(object sender, EventArgs e) | |||
{ | |||
LoadCurrentConfiguration(); | |||
@@ -285,7 +285,6 @@ namespace Shadowsocks.View | |||
PACModeItem.Checked = !config.global; | |||
ShareOverLANItem.Checked = config.shareOverLan; | |||
AutoStartupItem.Checked = AutoStartup.Check(); | |||
AvailabilityStatistics.Checked = config.availabilityStatistics; | |||
onlinePACItem.Checked = onlinePACItem.Enabled && config.useOnlinePac; | |||
localPACItem.Checked = !onlinePACItem.Checked; | |||
UpdatePACItemsEnabledStatus(); | |||
@@ -441,6 +440,12 @@ namespace Shadowsocks.View | |||
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) | |||
{ | |||
@@ -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) | |||
{ | |||
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> | |||
<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> | |||
<asmv3:application> | |||
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings"> | |||
<dpiAware>true</dpiAware> | |||
<dpiAware>True/PM</dpiAware> | |||
</asmv3:windowsSettings> | |||
</asmv3:application> | |||
</assembly> |
@@ -0,0 +1,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> | |||
<UseApplicationTrust>false</UseApplicationTrust> | |||
<BootstrapperEnabled>true</BootstrapperEnabled> | |||
<NuGetPackageImportStamp> | |||
</NuGetPackageImportStamp> | |||
</PropertyGroup> | |||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'"> | |||
<DebugSymbols>true</DebugSymbols> | |||
@@ -62,12 +64,56 @@ | |||
<ApplicationManifest>app.manifest</ApplicationManifest> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<Reference Include="Microsoft.CSharp" /> | |||
<Reference Include="Microsoft.Threading.Tasks, Version=1.0.12.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"> | |||
<HintPath>3rd\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll</HintPath> | |||
<Private>True</Private> | |||
<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="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.Data" /> | |||
<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.DataVisualization" /> | |||
<Reference Include="System.Xaml" /> | |||
<Reference Include="System.XML" /> | |||
<Reference Include="UIAutomationProvider" /> | |||
<Reference Include="WindowsBase" /> | |||
<Reference Include="WindowsFormsIntegration" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<Compile Include="3rd\zxing\BarcodeFormat.cs" /> | |||
@@ -125,7 +171,7 @@ | |||
<Compile Include="3rd\zxing\WriterException.cs" /> | |||
<Compile Include="Controller\Service\AvailabilityStatistics.cs" /> | |||
<Compile Include="Controller\Strategy\HighAvailabilityStrategy.cs" /> | |||
<Compile Include="Controller\Strategy\SimplyChooseByStatisticsStrategy.cs" /> | |||
<Compile Include="Controller\Strategy\StatisticsStrategy.cs" /> | |||
<Compile Include="Controller\System\AutoStartup.cs" /> | |||
<Compile Include="Controller\FileManager.cs" /> | |||
<Compile Include="Controller\Service\GFWListUpdater.cs" /> | |||
@@ -148,6 +194,7 @@ | |||
<Compile Include="Model\LogViewerConfig.cs" /> | |||
<Compile Include="Model\Server.cs" /> | |||
<Compile Include="Model\Configuration.cs" /> | |||
<Compile Include="Model\StatisticsStrategyConfiguration.cs" /> | |||
<Compile Include="Properties\Resources.Designer.cs"> | |||
<AutoGen>True</AutoGen> | |||
<DesignTime>True</DesignTime> | |||
@@ -169,6 +216,12 @@ | |||
<Compile Include="Properties\AssemblyInfo.cs" /> | |||
<Compile Include="Controller\ShadowsocksController.cs" /> | |||
<Compile Include="Controller\System\SystemProxy.cs" /> | |||
<Compile Include="View\CalculationControl.cs"> | |||
<SubType>UserControl</SubType> | |||
</Compile> | |||
<Compile Include="View\CalculationControl.Designer.cs"> | |||
<DependentUpon>CalculationControl.cs</DependentUpon> | |||
</Compile> | |||
<Compile Include="View\LogForm.cs"> | |||
<SubType>Form</SubType> | |||
</Compile> | |||
@@ -185,6 +238,12 @@ | |||
<Compile Include="View\QRCodeSplashForm.cs"> | |||
<SubType>Form</SubType> | |||
</Compile> | |||
<Compile Include="View\StatisticsStrategyConfigurationForm.cs"> | |||
<SubType>Form</SubType> | |||
</Compile> | |||
<Compile Include="View\StatisticsStrategyConfigurationForm.Designer.cs"> | |||
<DependentUpon>StatisticsStrategyConfigurationForm.cs</DependentUpon> | |||
</Compile> | |||
<EmbeddedResource Include="View\ConfigForm.resx"> | |||
<DependentUpon>ConfigForm.cs</DependentUpon> | |||
<SubType>Designer</SubType> | |||
@@ -194,12 +253,18 @@ | |||
<SubType>Designer</SubType> | |||
<LastGenOutput>Resources.Designer.cs</LastGenOutput> | |||
</EmbeddedResource> | |||
<EmbeddedResource Include="View\CalculationControl.resx"> | |||
<DependentUpon>CalculationControl.cs</DependentUpon> | |||
</EmbeddedResource> | |||
<EmbeddedResource Include="View\LogForm.resx"> | |||
<DependentUpon>LogForm.cs</DependentUpon> | |||
</EmbeddedResource> | |||
<EmbeddedResource Include="View\QRCodeForm.resx"> | |||
<DependentUpon>QRCodeForm.cs</DependentUpon> | |||
</EmbeddedResource> | |||
<EmbeddedResource Include="View\StatisticsStrategyConfigurationForm.resx"> | |||
<DependentUpon>StatisticsStrategyConfigurationForm.cs</DependentUpon> | |||
</EmbeddedResource> | |||
<None Include="app.config" /> | |||
<None Include="app.manifest"> | |||
<SubType>Designer</SubType> | |||
@@ -211,6 +276,8 @@ | |||
<None Include="Data\proxy.pac.txt.gz" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<None Include="packages.config" /> | |||
<None Include="Properties\DataSources\Shadowsocks.Model.StatisticsStrategyConfiguration.datasource" /> | |||
<None Include="Resources\ss20.png" /> | |||
<None Include="Resources\ss16.png" /> | |||
<None Include="Resources\ss24.png" /> | |||
@@ -218,6 +285,9 @@ | |||
<Content Include="Data\cn.txt" /> | |||
<Content Include="Data\privoxy_conf.txt" /> | |||
<Content Include="Data\user-rule.txt" /> | |||
<Content Include="FodyWeavers.xml"> | |||
<SubType>Designer</SubType> | |||
</Content> | |||
<Content Include="shadowsocks.ico" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
@@ -253,6 +323,18 @@ | |||
</BootstrapperPackage> | |||
</ItemGroup> | |||
<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. | |||
Other similar extension points exist, see Microsoft.Common.targets. | |||
<Target Name="BeforeBuild"> | |||