Browse Source

Merge pull request #340 from icylogic/feature/statistics_ui

Feature/statistics ui
tags/3.0
icylogic 9 years ago
parent
commit
ad8a8c24e6
22 changed files with 1731 additions and 258 deletions
  1. +6
    -0
      .gitignore
  2. +5
    -0
      nuget.config
  3. +251
    -53
      shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs
  4. +22
    -14
      shadowsocks-csharp/Controller/ShadowsocksController.cs
  5. +0
    -176
      shadowsocks-csharp/Controller/Strategy/SimplyChooseByStatisticsStrategy.cs
  6. +152
    -0
      shadowsocks-csharp/Controller/Strategy/StatisticsStrategy.cs
  7. +1
    -1
      shadowsocks-csharp/Controller/Strategy/StrategyManager.cs
  8. +5
    -0
      shadowsocks-csharp/FodyWeavers.xml
  9. +1
    -0
      shadowsocks-csharp/Model/Configuration.cs
  10. +107
    -0
      shadowsocks-csharp/Model/StatisticsStrategyConfiguration.cs
  11. +10
    -0
      shadowsocks-csharp/Properties/DataSources/Shadowsocks.Model.StatisticsStrategyConfiguration.datasource
  12. +111
    -0
      shadowsocks-csharp/View/CalculationControl.Designer.cs
  13. +25
    -0
      shadowsocks-csharp/View/CalculationControl.cs
  14. +120
    -0
      shadowsocks-csharp/View/CalculationControl.resx
  15. +8
    -8
      shadowsocks-csharp/View/MenuViewController.cs
  16. +531
    -0
      shadowsocks-csharp/View/StatisticsStrategyConfigurationForm.Designer.cs
  17. +140
    -0
      shadowsocks-csharp/View/StatisticsStrategyConfigurationForm.cs
  18. +126
    -0
      shadowsocks-csharp/View/StatisticsStrategyConfigurationForm.resx
  19. +17
    -4
      shadowsocks-csharp/app.config
  20. +1
    -1
      shadowsocks-csharp/app.manifest
  21. +9
    -0
      shadowsocks-csharp/packages.config
  22. +83
    -1
      shadowsocks-csharp/shadowsocks-csharp.csproj

+ 6
- 0
.gitignore View File

@@ -5,3 +5,9 @@ shadowsocks-csharp/shadowsocks-csharp.csproj.user
TestResults TestResults
*.suo *.suo


shadowsocks-csharp/3rd/*
!shadowsocks-csharp/3rd/zxing/
!shadowsocks-csharp/3rd/SimpleJson.cs
packages/*

shadowsocks-csharp.sln.DotSettings.user

+ 5
- 0
nuget.config View File

@@ -0,0 +1,5 @@
<configuration>
<config>
<add key="repositoryPath" value="shadowsocks-csharp\3rd" />
</config>
</configuration>

+ 251
- 53
shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs View File

@@ -1,50 +1,72 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using Shadowsocks.Model; using Shadowsocks.Model;
using System.Reflection;
using Shadowsocks.Util; using Shadowsocks.Util;


namespace Shadowsocks.Controller namespace Shadowsocks.Controller
{ {
class AvailabilityStatistics
using DataUnit = KeyValuePair<string, string>;
using DataList = List<KeyValuePair<string, string>>;

using Statistics = Dictionary<string, List<AvailabilityStatistics.RawStatisticsData>>;

public class AvailabilityStatistics
{ {
private static readonly string StatisticsFilesName = "shadowsocks.availability.csv";
private static readonly string Delimiter = ",";
private static readonly int Timeout = 500;
private static readonly int Repeat = 4; //repeat times every evaluation
private static readonly int Interval = 10 * 60 * 1000; //evaluate proxies every 15 minutes
private Timer timer = null;
private State state = null;
private List<Server> servers;
public static readonly string DateTimePattern = "yyyy-MM-dd HH:mm:ss";
private const string StatisticsFilesName = "shadowsocks.availability.csv";
private const string Delimiter = ",";
private const int Timeout = 500;
private const int DelayBeforeStart = 1000;
public Statistics RawStatistics { get; private set; }
public Statistics FilteredStatistics { get; private set; }
public static readonly DateTime UnknownDateTime = new DateTime(1970, 1, 1);
private int Repeat => _config.RepeatTimesNum;
private const int RetryInterval = 2*60*1000; //retry 2 minutes after failed
private int Interval => (int) TimeSpan.FromMinutes(_config.DataCollectionMinutes).TotalMilliseconds;
private Timer _timer;
private State _state;
private List<Server> _servers;
private StatisticsStrategyConfiguration _config;


public static string AvailabilityStatisticsFile; public static string AvailabilityStatisticsFile;


//static constructor to initialize every public static fields before refereced //static constructor to initialize every public static fields before refereced
static AvailabilityStatistics() static AvailabilityStatistics()
{ {
string temppath = Utils.GetTempPath();
var temppath = Utils.GetTempPath();
AvailabilityStatisticsFile = Path.Combine(temppath, StatisticsFilesName); AvailabilityStatisticsFile = Path.Combine(temppath, StatisticsFilesName);
} }


public bool Set(bool enabled)
public AvailabilityStatistics(Configuration config, StatisticsStrategyConfiguration statisticsConfig)
{
UpdateConfiguration(config, statisticsConfig);
}

public bool Set(StatisticsStrategyConfiguration config)
{ {
_config = config;
try try
{ {
if (enabled)
if (config.StatisticsEnabled)
{ {
if (timer?.Change(0, Interval) == null)
if (_timer?.Change(DelayBeforeStart, Interval) == null)
{ {
state = new State();
timer = new Timer(Evaluate, state, 0, Interval);
_state = new State();
_timer = new Timer(Run, _state, DelayBeforeStart, Interval);
} }
} }
else else
{ {
timer?.Dispose();
_timer?.Dispose();
} }
return true; return true;
} }
@@ -55,64 +77,240 @@ namespace Shadowsocks.Controller
} }
} }


private void Evaluate(object obj)
//hardcode
//TODO: backup reliable isp&geolocation provider or a local database is required
public static async Task<DataList> GetGeolocationAndIsp()
{ {
Ping ping = new Ping();
State state = (State) obj;
foreach (var server in servers)
Logging.Debug("Retrive information of geolocation and isp");
const string API = "http://ip-api.com/json";
const string alternativeAPI = "http://www.telize.com/geoip"; //must be comptible with current API
var result = await GetInfoFromAPI(API);
if (result != null) return result;
result = await GetInfoFromAPI(alternativeAPI);
if (result != null) return result;
return new DataList
{
new DataUnit(State.Geolocation, State.Unknown),
new DataUnit(State.ISP, State.Unknown)
};
}

private static async Task<DataList> GetInfoFromAPI(string API)
{
string jsonString;
try
{ {
Logging.Debug("eveluating " + server.FriendlyName());
foreach (var _ in Enumerable.Range(0, Repeat))
jsonString = await new HttpClient().GetStringAsync(API);
}
catch (HttpRequestException e)
{
Logging.LogUsefulException(e);
return null;
}
dynamic obj;
if (!SimpleJson.SimpleJson.TryDeserializeObject(jsonString, out obj)) return null;
string country = obj["country"];
string city = obj["city"];
string isp = obj["isp"];
if (country == null || city == null || isp == null) return null;
return new DataList {
new DataUnit(State.Geolocation, $"\"{country} {city}\""),
new DataUnit(State.ISP, $"\"{isp}\"")
};
}

private async Task<List<DataList>> ICMPTest(Server server)
{
Logging.Debug("Ping " + server.FriendlyName());
if (server.server == "") return null;
var IP = Dns.GetHostAddresses(server.server).First(ip => ip.AddressFamily == AddressFamily.InterNetwork);
var ping = new Ping();
var ret = new List<DataList>();
foreach (
var timestamp in Enumerable.Range(0, Repeat).Select(_ => DateTime.Now.ToString(DateTimePattern)))
{
//ICMP echo. we can also set options and special bytes
try
{ {
//TODO: do simple analyze of data to provide friendly message, like package loss.
string timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
//ICMP echo. we can also set options and special bytes
//seems no need to use SendPingAsync
try
{
PingReply reply = ping.Send(server.server, Timeout);
state.data = new List<KeyValuePair<string, string>>();
state.data.Add(new KeyValuePair<string, string>("Timestamp", timestamp));
state.data.Add(new KeyValuePair<string, string>("Server", server.FriendlyName()));
state.data.Add(new KeyValuePair<string, string>("Status", reply.Status.ToString()));
state.data.Add(new KeyValuePair<string, string>("RoundtripTime", reply.RoundtripTime.ToString()));
//state.data.Add(new KeyValuePair<string, string>("data", reply.Buffer.ToString())); // The data of reply
Append(state.data);
}
catch (Exception e)
var reply = await ping.SendTaskAsync(IP, Timeout);
ret.Add(new List<KeyValuePair<string, string>>
{ {
Console.WriteLine($"An exception occured when eveluating {server.FriendlyName()}");
Logging.LogUsefulException(e);
}
new KeyValuePair<string, string>("Timestamp", timestamp),
new KeyValuePair<string, string>("Server", server.FriendlyName()),
new KeyValuePair<string, string>("Status", reply?.Status.ToString()),
new KeyValuePair<string, string>("RoundtripTime", reply?.RoundtripTime.ToString())
//new KeyValuePair<string, string>("data", reply.Buffer.ToString()); // The data of reply
});
Thread.Sleep(Timeout + new Random().Next() % Timeout);
//Do ICMPTest in a random frequency
}
catch (Exception e)
{
Console.WriteLine($"An exception occured when eveluating {server.FriendlyName()}");
Logging.LogUsefulException(e);
} }
} }
return ret;
}

private void Run(object obj)
{
LoadRawStatistics();
FilterRawStatistics();
evaluate();
} }


private static void Append(List<KeyValuePair<string, string>> data)
private async void evaluate()
{ {
string dataLine = string.Join(Delimiter, data.Select(kv => kv.Value).ToArray());
var geolocationAndIsp = GetGeolocationAndIsp();
foreach (var dataLists in await TaskEx.WhenAll(_servers.Select(ICMPTest)))
{
if (dataLists == null) continue;
foreach (var dataList in dataLists.Where(dataList => dataList != null))
{
await geolocationAndIsp;
Append(dataList, geolocationAndIsp.Result);
}
}
}

private static void Append(DataList dataList, IEnumerable<DataUnit> extra)
{
var data = dataList.Concat(extra);
var dataLine = string.Join(Delimiter, data.Select(kv => kv.Value).ToArray());
string[] lines; string[] lines;
if (!File.Exists(AvailabilityStatisticsFile)) if (!File.Exists(AvailabilityStatisticsFile))
{ {
string headerLine = string.Join(Delimiter, data.Select(kv => kv.Key).ToArray());
lines = new string[] { headerLine, dataLine };
var headerLine = string.Join(Delimiter, data.Select(kv => kv.Key).ToArray());
lines = new[] {headerLine, dataLine};
} }
else else
{ {
lines = new string[] { dataLine };
lines = new[] {dataLine};
}
try
{
File.AppendAllLines(AvailabilityStatisticsFile, lines);
}
catch (IOException e)
{
Logging.LogUsefulException(e);
} }
File.AppendAllLines(AvailabilityStatisticsFile, lines);
} }


internal void UpdateConfiguration(Configuration _config)
internal void UpdateConfiguration(Configuration config, StatisticsStrategyConfiguration statisticsConfig)
{ {
Set(_config.availabilityStatistics);
servers = _config.configs;
Set(statisticsConfig);
_servers = config.configs;
} }


private class State
private async void FilterRawStatistics()
{ {
public List<KeyValuePair<string, string>> data = new List<KeyValuePair<string, string>>();
if (RawStatistics == null) return;
if (FilteredStatistics == null)
{
FilteredStatistics = new Statistics();
}
foreach (IEnumerable<RawStatisticsData> rawData in RawStatistics.Values)
{
var filteredData = rawData;
if (_config.ByIsp)
{
var current = await GetGeolocationAndIsp();
filteredData =
filteredData.Where(
data =>
data.Geolocation == current[0].Value ||
data.Geolocation == State.Unknown);
filteredData =
filteredData.Where(
data => data.ISP == current[1].Value || data.ISP == State.Unknown);
if (filteredData.LongCount() == 0) return;
}
if (_config.ByHourOfDay)
{
var currentHour = DateTime.Now.Hour;
filteredData = filteredData.Where(data =>
data.Timestamp != UnknownDateTime && data.Timestamp.Hour.Equals(currentHour)
);
if (filteredData.LongCount() == 0) return;
}
var dataList = filteredData as List<RawStatisticsData> ?? filteredData.ToList();
var serverName = dataList[0].ServerName;
FilteredStatistics[serverName] = dataList;
}
} }

private void LoadRawStatistics()
{
try
{
var path = AvailabilityStatisticsFile;
Logging.Debug($"loading statistics from {path}");
if (!File.Exists(path))
{
Console.WriteLine($"statistics file does not exist, try to reload {RetryInterval/60/1000} minutes later");
_timer.Change(RetryInterval, Interval);
return;
}
RawStatistics = (from l in File.ReadAllLines(path)
.Skip(1)
let strings = l.Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries)
let rawData = new RawStatisticsData
{
Timestamp = ParseExactOrUnknown(strings[0]),
ServerName = strings[1],
ICMPStatus = strings[2],
RoundtripTime = int.Parse(strings[3]),
Geolocation = 5 > strings.Length ?
null
: strings[4],
ISP = 6 > strings.Length ? null : strings[5]
}
group rawData by rawData.ServerName into server
select new
{
ServerName = server.Key,
data = server.ToList()
}).ToDictionary(server => server.ServerName, server=> server.data);
}
catch (Exception e)
{
Logging.LogUsefulException(e);
}
}

private DateTime ParseExactOrUnknown(string str)
{
DateTime dateTime;
return !DateTime.TryParseExact(str, DateTimePattern, null, DateTimeStyles.None, out dateTime) ? UnknownDateTime : dateTime;
}

public class State
{
public DataList dataList = new DataList();
public const string Geolocation = "Geolocation";
public const string ISP = "ISP";
public const string Unknown = "Unknown";
}

public class RawStatisticsData
{
public DateTime Timestamp;
public string ServerName;
public string ICMPStatus;
public int RoundtripTime;
public string Geolocation;
public string ISP ;
}

public class StatisticsData
{
public float PackageLoss;
public int AverageResponse;
public int MinResponse;
public int MaxResponse;
}

} }
} }

+ 22
- 14
shadowsocks-csharp/Controller/ShadowsocksController.cs View File

@@ -25,7 +25,9 @@ namespace Shadowsocks.Controller
private StrategyManager _strategyManager; private StrategyManager _strategyManager;
private PolipoRunner polipoRunner; private PolipoRunner polipoRunner;
private GFWListUpdater gfwListUpdater; private GFWListUpdater gfwListUpdater;
private AvailabilityStatistics _availabilityStatics;
public AvailabilityStatistics availabilityStatistics { get; private set; }
public StatisticsStrategyConfiguration StatisticsConfiguration { get; private set; }
private bool stopped = false; private bool stopped = false;
private bool _systemProxyIsDirty = false; private bool _systemProxyIsDirty = false;
@@ -53,10 +55,12 @@ namespace Shadowsocks.Controller
public ShadowsocksController() public ShadowsocksController()
{ {
_config = Configuration.Load(); _config = Configuration.Load();
StatisticsConfiguration = StatisticsStrategyConfiguration.Load();
_strategyManager = new StrategyManager(this); _strategyManager = new StrategyManager(this);
StartReleasingMemory(); StartReleasingMemory();
} }
public void Start() public void Start()
{ {
Reload(); Reload();
@@ -125,6 +129,12 @@ namespace Shadowsocks.Controller
Configuration.Save(_config); Configuration.Save(_config);
} }
public void SaveStrategyConfigurations(StatisticsStrategyConfiguration configuration)
{
StatisticsConfiguration = configuration;
StatisticsStrategyConfiguration.Save(configuration);
}
public bool AddServerBySSURL(string ssURL) public bool AddServerBySSURL(string ssURL)
{ {
try try
@@ -248,14 +258,12 @@ namespace Shadowsocks.Controller
} }
} }
public void ToggleAvailabilityStatistics(bool enabled)
public void UpdateStatisticsConfiguration(bool enabled)
{ {
if (_availabilityStatics != null)
{
_availabilityStatics.Set(enabled);
_config.availabilityStatistics = enabled;
SaveConfig(_config);
}
if (availabilityStatistics == null) return;
availabilityStatistics.UpdateConfiguration(_config, StatisticsConfiguration);
_config.availabilityStatistics = enabled;
SaveConfig(_config);
} }
public void SavePACUrl(string pacUrl) public void SavePACUrl(string pacUrl)
@@ -296,6 +304,7 @@ namespace Shadowsocks.Controller
{ {
// some logic in configuration updated the config when saving, we need to read it again // some logic in configuration updated the config when saving, we need to read it again
_config = Configuration.Load(); _config = Configuration.Load();
StatisticsConfiguration = StatisticsStrategyConfiguration.Load();
if (polipoRunner == null) if (polipoRunner == null)
{ {
@@ -314,17 +323,16 @@ namespace Shadowsocks.Controller
gfwListUpdater.Error += pacServer_PACUpdateError; gfwListUpdater.Error += pacServer_PACUpdateError;
} }
if (_listener != null)
if (availabilityStatistics == null)
{ {
_listener.Stop();
availabilityStatistics = new AvailabilityStatistics(_config, StatisticsConfiguration);
} }
availabilityStatistics.UpdateConfiguration(_config, StatisticsConfiguration);
if (_availabilityStatics == null)
if (_listener != null)
{ {
_availabilityStatics = new AvailabilityStatistics();
_availabilityStatics.UpdateConfiguration(_config);
_listener.Stop();
} }
// don't put polipoRunner.Start() before pacServer.Stop() // don't put polipoRunner.Start() before pacServer.Stop()
// or bind will fail when switching bind address from 0.0.0.0 to 127.0.0.1 // or bind will fail when switching bind address from 0.0.0.0 to 127.0.0.1
// though UseShellExecute is set to true now // though UseShellExecute is set to true now


+ 0
- 176
shadowsocks-csharp/Controller/Strategy/SimplyChooseByStatisticsStrategy.cs View File

@@ -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
}

}
}

+ 152
- 0
shadowsocks-csharp/Controller/Strategy/StatisticsStrategy.cs View File

@@ -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
}

}
}

+ 1
- 1
shadowsocks-csharp/Controller/Strategy/StrategyManager.cs View File

@@ -13,7 +13,7 @@ namespace Shadowsocks.Controller.Strategy
_strategies = new List<IStrategy>(); _strategies = new List<IStrategy>();
_strategies.Add(new BalancingStrategy(controller)); _strategies.Add(new BalancingStrategy(controller));
_strategies.Add(new HighAvailabilityStrategy(controller)); _strategies.Add(new HighAvailabilityStrategy(controller));
_strategies.Add(new SimplyChooseByStatisticsStrategy(controller));
_strategies.Add(new StatisticsStrategy(controller));
// TODO: load DLL plugins // TODO: load DLL plugins
} }
public IList<IStrategy> GetStrategies() public IList<IStrategy> GetStrategies()


+ 5
- 0
shadowsocks-csharp/FodyWeavers.xml View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Weavers>
<Costura/>
</Weavers>

+ 1
- 0
shadowsocks-csharp/Model/Configuration.cs View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Windows.Forms; using System.Windows.Forms;
using SimpleJson;
namespace Shadowsocks.Model namespace Shadowsocks.Model
{ {


+ 107
- 0
shadowsocks-csharp/Model/StatisticsStrategyConfiguration.cs View File

@@ -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; }
}
}
}

+ 10
- 0
shadowsocks-csharp/Properties/DataSources/Shadowsocks.Model.StatisticsStrategyConfiguration.datasource View File

@@ -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>

+ 111
- 0
shadowsocks-csharp/View/CalculationControl.Designer.cs View File

@@ -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;
}
}

+ 25
- 0
shadowsocks-csharp/View/CalculationControl.cs View File

@@ -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;
}
}

+ 120
- 0
shadowsocks-csharp/View/CalculationControl.resx View File

@@ -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>

+ 8
- 8
shadowsocks-csharp/View/MenuViewController.cs View File

@@ -30,7 +30,6 @@ namespace Shadowsocks.View
private MenuItem enableItem; private MenuItem enableItem;
private MenuItem modeItem; private MenuItem modeItem;
private MenuItem AutoStartupItem; private MenuItem AutoStartupItem;
private MenuItem AvailabilityStatistics;
private MenuItem ShareOverLANItem; private MenuItem ShareOverLANItem;
private MenuItem SeperatorItem; private MenuItem SeperatorItem;
private MenuItem ConfigItem; private MenuItem ConfigItem;
@@ -172,6 +171,7 @@ namespace Shadowsocks.View
this.ServersItem = CreateMenuGroup("Servers", new MenuItem[] { this.ServersItem = CreateMenuGroup("Servers", new MenuItem[] {
this.SeperatorItem = new MenuItem("-"), this.SeperatorItem = new MenuItem("-"),
this.ConfigItem = CreateMenuItem("Edit Servers...", new EventHandler(this.Config_Click)), this.ConfigItem = CreateMenuItem("Edit Servers...", new EventHandler(this.Config_Click)),
CreateMenuItem("Statistics Config...", StatisticsConfigItem_Click),
CreateMenuItem("Show QRCode...", new EventHandler(this.QRCodeItem_Click)), CreateMenuItem("Show QRCode...", new EventHandler(this.QRCodeItem_Click)),
CreateMenuItem("Scan QRCode from Screen...", new EventHandler(this.ScanQRCodeItem_Click)) CreateMenuItem("Scan QRCode from Screen...", new EventHandler(this.ScanQRCodeItem_Click))
}), }),
@@ -186,7 +186,6 @@ namespace Shadowsocks.View
}), }),
new MenuItem("-"), new MenuItem("-"),
this.AutoStartupItem = CreateMenuItem("Start on Boot", new EventHandler(this.AutoStartupItem_Click)), this.AutoStartupItem = CreateMenuItem("Start on Boot", new EventHandler(this.AutoStartupItem_Click)),
this.AvailabilityStatistics = CreateMenuItem("Availability Statistics", new EventHandler(this.AvailabilityStatisticsItem_Click)),
this.ShareOverLANItem = CreateMenuItem("Allow Clients from LAN", new EventHandler(this.ShareOverLANItem_Click)), this.ShareOverLANItem = CreateMenuItem("Allow Clients from LAN", new EventHandler(this.ShareOverLANItem_Click)),
new MenuItem("-"), new MenuItem("-"),
CreateMenuItem("Show Logs...", new EventHandler(this.ShowLogItem_Click)), CreateMenuItem("Show Logs...", new EventHandler(this.ShowLogItem_Click)),
@@ -201,6 +200,7 @@ namespace Shadowsocks.View
}); });
} }
private void controller_ConfigChanged(object sender, EventArgs e) private void controller_ConfigChanged(object sender, EventArgs e)
{ {
LoadCurrentConfiguration(); LoadCurrentConfiguration();
@@ -285,7 +285,6 @@ namespace Shadowsocks.View
PACModeItem.Checked = !config.global; PACModeItem.Checked = !config.global;
ShareOverLANItem.Checked = config.shareOverLan; ShareOverLANItem.Checked = config.shareOverLan;
AutoStartupItem.Checked = AutoStartup.Check(); AutoStartupItem.Checked = AutoStartup.Check();
AvailabilityStatistics.Checked = config.availabilityStatistics;
onlinePACItem.Checked = onlinePACItem.Enabled && config.useOnlinePac; onlinePACItem.Checked = onlinePACItem.Enabled && config.useOnlinePac;
localPACItem.Checked = !onlinePACItem.Checked; localPACItem.Checked = !onlinePACItem.Checked;
UpdatePACItemsEnabledStatus(); UpdatePACItemsEnabledStatus();
@@ -441,6 +440,12 @@ namespace Shadowsocks.View
new LogForm(controller, argument).Show(); new LogForm(controller, argument).Show();
} }
private void StatisticsConfigItem_Click(object sender, EventArgs e)
{
StatisticsStrategyConfigurationForm form = new StatisticsStrategyConfigurationForm(controller);
form.Show();
}
private void QRCodeItem_Click(object sender, EventArgs e) private void QRCodeItem_Click(object sender, EventArgs e)
{ {
@@ -551,11 +556,6 @@ namespace Shadowsocks.View
} }
} }
private void AvailabilityStatisticsItem_Click(object sender, EventArgs e) {
AvailabilityStatistics.Checked = !AvailabilityStatistics.Checked;
controller.ToggleAvailabilityStatistics(AvailabilityStatistics.Checked);
}
private void LocalPACItem_Click(object sender, EventArgs e) private void LocalPACItem_Click(object sender, EventArgs e)
{ {
if (!localPACItem.Checked) if (!localPACItem.Checked)


+ 531
- 0
shadowsocks-csharp/View/StatisticsStrategyConfigurationForm.Designer.cs View File

@@ -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;
}
}

+ 140
- 0
shadowsocks-csharp/View/StatisticsStrategyConfigurationForm.cs View File

@@ -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();
}
}
}

+ 126
- 0
shadowsocks-csharp/View/StatisticsStrategyConfigurationForm.resx View File

@@ -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>

+ 17
- 4
shadowsocks-csharp/app.config View File

@@ -1,6 +1,19 @@
<?xml version="1.0"?>
<?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<startup> <startup>
<supportedRuntime version="v4.0"/>
<supportedRuntime version="v2.0.50727"/>
</startup></configuration>
<supportedRuntime version="v4.0" />
<supportedRuntime version="v2.0.50727" />
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Runtime" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2.6.8.0" newVersion="2.6.8.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Threading.Tasks" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2.6.8.0" newVersion="2.6.8.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

+ 1
- 1
shadowsocks-csharp/app.manifest View File

@@ -10,7 +10,7 @@
</trustInfo> </trustInfo>
<asmv3:application> <asmv3:application>
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings"> <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
<dpiAware>true</dpiAware>
<dpiAware>True/PM</dpiAware>
</asmv3:windowsSettings> </asmv3:windowsSettings>
</asmv3:application> </asmv3:application>
</assembly> </assembly>

+ 9
- 0
shadowsocks-csharp/packages.config View File

@@ -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>

+ 83
- 1
shadowsocks-csharp/shadowsocks-csharp.csproj View File

@@ -36,6 +36,8 @@
<ApplicationVersion>1.0.0.%2a</ApplicationVersion> <ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<UseApplicationTrust>false</UseApplicationTrust> <UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled> <BootstrapperEnabled>true</BootstrapperEnabled>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
@@ -62,12 +64,56 @@
<ApplicationManifest>app.manifest</ApplicationManifest> <ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Microsoft.CSharp" />
<Reference Include="Microsoft.Threading.Tasks, Version=1.0.12.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>3rd\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll</HintPath>
<Private>True</Private>
<EmbedInteropTypes>False</EmbedInteropTypes>
</Reference>
<Reference Include="Microsoft.Threading.Tasks.Extensions, Version=1.0.12.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>3rd\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll</HintPath>
<Private>True</Private>
<EmbedInteropTypes>False</EmbedInteropTypes>
</Reference>
<Reference Include="Microsoft.Threading.Tasks.Extensions.Desktop, Version=1.0.168.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>3rd\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll</HintPath>
<Private>True</Private>
<EmbedInteropTypes>False</EmbedInteropTypes>
</Reference>
<Reference Include="Microsoft.VisualBasic" /> <Reference Include="Microsoft.VisualBasic" />
<Reference Include="Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>3rd\Newtonsoft.Json.7.0.1\lib\net40\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Data" /> <Reference Include="System.Data" />
<Reference Include="System.Drawing" /> <Reference Include="System.Drawing" />
<Reference Include="System.IO, Version=2.6.8.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>3rd\Microsoft.Bcl.1.1.8\lib\net40\System.IO.dll</HintPath>
<Private>True</Private>
<EmbedInteropTypes>False</EmbedInteropTypes>
</Reference>
<Reference Include="System.Net" />
<Reference Include="System.Net.Http, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
<Reference Include="System.Runtime, Version=2.6.8.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>3rd\Microsoft.Bcl.1.1.8\lib\net40\System.Runtime.dll</HintPath>
<Private>True</Private>
<EmbedInteropTypes>False</EmbedInteropTypes>
</Reference>
<Reference Include="System.Threading.Tasks, Version=2.6.8.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>3rd\Microsoft.Bcl.1.1.8\lib\net40\System.Threading.Tasks.dll</HintPath>
<Private>True</Private>
<EmbedInteropTypes>False</EmbedInteropTypes>
</Reference>
<Reference Include="System.Windows.Forms" /> <Reference Include="System.Windows.Forms" />
<Reference Include="System.Windows.Forms.DataVisualization" />
<Reference Include="System.Xaml" />
<Reference Include="System.XML" /> <Reference Include="System.XML" />
<Reference Include="UIAutomationProvider" />
<Reference Include="WindowsBase" />
<Reference Include="WindowsFormsIntegration" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="3rd\zxing\BarcodeFormat.cs" /> <Compile Include="3rd\zxing\BarcodeFormat.cs" />
@@ -125,7 +171,7 @@
<Compile Include="3rd\zxing\WriterException.cs" /> <Compile Include="3rd\zxing\WriterException.cs" />
<Compile Include="Controller\Service\AvailabilityStatistics.cs" /> <Compile Include="Controller\Service\AvailabilityStatistics.cs" />
<Compile Include="Controller\Strategy\HighAvailabilityStrategy.cs" /> <Compile Include="Controller\Strategy\HighAvailabilityStrategy.cs" />
<Compile Include="Controller\Strategy\SimplyChooseByStatisticsStrategy.cs" />
<Compile Include="Controller\Strategy\StatisticsStrategy.cs" />
<Compile Include="Controller\System\AutoStartup.cs" /> <Compile Include="Controller\System\AutoStartup.cs" />
<Compile Include="Controller\FileManager.cs" /> <Compile Include="Controller\FileManager.cs" />
<Compile Include="Controller\Service\GFWListUpdater.cs" /> <Compile Include="Controller\Service\GFWListUpdater.cs" />
@@ -148,6 +194,7 @@
<Compile Include="Model\LogViewerConfig.cs" /> <Compile Include="Model\LogViewerConfig.cs" />
<Compile Include="Model\Server.cs" /> <Compile Include="Model\Server.cs" />
<Compile Include="Model\Configuration.cs" /> <Compile Include="Model\Configuration.cs" />
<Compile Include="Model\StatisticsStrategyConfiguration.cs" />
<Compile Include="Properties\Resources.Designer.cs"> <Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen> <AutoGen>True</AutoGen>
<DesignTime>True</DesignTime> <DesignTime>True</DesignTime>
@@ -169,6 +216,12 @@
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Controller\ShadowsocksController.cs" /> <Compile Include="Controller\ShadowsocksController.cs" />
<Compile Include="Controller\System\SystemProxy.cs" /> <Compile Include="Controller\System\SystemProxy.cs" />
<Compile Include="View\CalculationControl.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="View\CalculationControl.Designer.cs">
<DependentUpon>CalculationControl.cs</DependentUpon>
</Compile>
<Compile Include="View\LogForm.cs"> <Compile Include="View\LogForm.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
</Compile> </Compile>
@@ -185,6 +238,12 @@
<Compile Include="View\QRCodeSplashForm.cs"> <Compile Include="View\QRCodeSplashForm.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
</Compile> </Compile>
<Compile Include="View\StatisticsStrategyConfigurationForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="View\StatisticsStrategyConfigurationForm.Designer.cs">
<DependentUpon>StatisticsStrategyConfigurationForm.cs</DependentUpon>
</Compile>
<EmbeddedResource Include="View\ConfigForm.resx"> <EmbeddedResource Include="View\ConfigForm.resx">
<DependentUpon>ConfigForm.cs</DependentUpon> <DependentUpon>ConfigForm.cs</DependentUpon>
<SubType>Designer</SubType> <SubType>Designer</SubType>
@@ -194,12 +253,18 @@
<SubType>Designer</SubType> <SubType>Designer</SubType>
<LastGenOutput>Resources.Designer.cs</LastGenOutput> <LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource> </EmbeddedResource>
<EmbeddedResource Include="View\CalculationControl.resx">
<DependentUpon>CalculationControl.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="View\LogForm.resx"> <EmbeddedResource Include="View\LogForm.resx">
<DependentUpon>LogForm.cs</DependentUpon> <DependentUpon>LogForm.cs</DependentUpon>
</EmbeddedResource> </EmbeddedResource>
<EmbeddedResource Include="View\QRCodeForm.resx"> <EmbeddedResource Include="View\QRCodeForm.resx">
<DependentUpon>QRCodeForm.cs</DependentUpon> <DependentUpon>QRCodeForm.cs</DependentUpon>
</EmbeddedResource> </EmbeddedResource>
<EmbeddedResource Include="View\StatisticsStrategyConfigurationForm.resx">
<DependentUpon>StatisticsStrategyConfigurationForm.cs</DependentUpon>
</EmbeddedResource>
<None Include="app.config" /> <None Include="app.config" />
<None Include="app.manifest"> <None Include="app.manifest">
<SubType>Designer</SubType> <SubType>Designer</SubType>
@@ -211,6 +276,8 @@
<None Include="Data\proxy.pac.txt.gz" /> <None Include="Data\proxy.pac.txt.gz" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="packages.config" />
<None Include="Properties\DataSources\Shadowsocks.Model.StatisticsStrategyConfiguration.datasource" />
<None Include="Resources\ss20.png" /> <None Include="Resources\ss20.png" />
<None Include="Resources\ss16.png" /> <None Include="Resources\ss16.png" />
<None Include="Resources\ss24.png" /> <None Include="Resources\ss24.png" />
@@ -218,6 +285,9 @@
<Content Include="Data\cn.txt" /> <Content Include="Data\cn.txt" />
<Content Include="Data\privoxy_conf.txt" /> <Content Include="Data\privoxy_conf.txt" />
<Content Include="Data\user-rule.txt" /> <Content Include="Data\user-rule.txt" />
<Content Include="FodyWeavers.xml">
<SubType>Designer</SubType>
</Content>
<Content Include="shadowsocks.ico" /> <Content Include="shadowsocks.ico" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -253,6 +323,18 @@
</BootstrapperPackage> </BootstrapperPackage>
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="3rd\Microsoft.Bcl.Build.1.0.14\tools\Microsoft.Bcl.Build.targets" Condition="Exists('3rd\Microsoft.Bcl.Build.1.0.14\tools\Microsoft.Bcl.Build.targets')" />
<Target Name="EnsureBclBuildImported" BeforeTargets="BeforeBuild" Condition="'$(BclBuildImported)' == ''">
<Error Condition="!Exists('3rd\Microsoft.Bcl.Build.1.0.14\tools\Microsoft.Bcl.Build.targets')" Text="This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=317567." HelpKeyword="BCLBUILD2001" />
<Error Condition="Exists('3rd\Microsoft.Bcl.Build.1.0.14\tools\Microsoft.Bcl.Build.targets')" Text="The build restored NuGet packages. Build the project again to include these packages in the build. For more information, see http://go.microsoft.com/fwlink/?LinkID=317568." HelpKeyword="BCLBUILD2002" />
</Target>
<Import Project="3rd\Fody.1.29.3\build\portable-net+sl+win+wpa+wp\Fody.targets" Condition="Exists('3rd\Fody.1.29.3\build\portable-net+sl+win+wpa+wp\Fody.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('3rd\Fody.1.29.3\build\portable-net+sl+win+wpa+wp\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '3rd\Fody.1.29.3\build\portable-net+sl+win+wpa+wp\Fody.targets'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild"> <Target Name="BeforeBuild">


Loading…
Cancel
Save