Browse Source

🧹 Remove statistics strategy

tags/4.3.1.0
database64128 3 years ago
parent
commit
5c6e5b927c
No known key found for this signature in database GPG Key ID: 1CA27546BEDB8B01
17 changed files with 1 additions and 2079 deletions
  1. +0
    -534
      shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs
  2. +1
    -42
      shadowsocks-csharp/Controller/ShadowsocksController.cs
  3. +0
    -170
      shadowsocks-csharp/Controller/Strategy/StatisticsStrategy.cs
  4. +0
    -1
      shadowsocks-csharp/Controller/Strategy/StrategyManager.cs
  5. +0
    -32
      shadowsocks-csharp/Data/i18n.csv
  6. +0
    -2
      shadowsocks-csharp/Model/Configuration.cs
  7. +0
    -95
      shadowsocks-csharp/Model/StatisticsRecord.cs
  8. +0
    -69
      shadowsocks-csharp/Model/StatisticsStrategyConfiguration.cs
  9. +0
    -10
      shadowsocks-csharp/Properties/DataSources/Shadowsocks.Model.StatisticsStrategyConfiguration.datasource
  10. +0
    -113
      shadowsocks-csharp/View/CalculationControl.Designer.cs
  11. +0
    -25
      shadowsocks-csharp/View/CalculationControl.cs
  12. +0
    -120
      shadowsocks-csharp/View/CalculationControl.resx
  13. +0
    -7
      shadowsocks-csharp/View/MenuViewController.cs
  14. +0
    -537
      shadowsocks-csharp/View/StatisticsStrategyConfigurationForm.Designer.cs
  15. +0
    -170
      shadowsocks-csharp/View/StatisticsStrategyConfigurationForm.cs
  16. +0
    -129
      shadowsocks-csharp/View/StatisticsStrategyConfigurationForm.resx
  17. +0
    -23
      shadowsocks-csharp/shadowsocks-csharp.csproj

+ 0
- 534
shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs View File

@@ -1,534 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using NLog;
using Shadowsocks.Model;
using Shadowsocks.Util;
namespace Shadowsocks.Controller
{
using Statistics = Dictionary<string, List<StatisticsRecord>>;
public sealed class AvailabilityStatistics : IDisposable
{
private static Logger logger = LogManager.GetCurrentClassLogger();
public const string DateTimePattern = "yyyy-MM-dd HH:mm:ss";
private const string StatisticsFilesName = "shadowsocks.availability.json";
public static string AvailabilityStatisticsFile;
//static constructor to initialize every public static fields before refereced
static AvailabilityStatistics()
{
AvailabilityStatisticsFile = Utils.GetTempPath(StatisticsFilesName);
}
//arguments for ICMP tests
private int Repeat => Config.RepeatTimesNum;
public const int TimeoutMilliseconds = 500;
//records cache for current server in {_monitorInterval} minutes
private readonly ConcurrentDictionary<string, List<int>> _latencyRecords = new ConcurrentDictionary<string, List<int>>();
//speed in KiB/s
private readonly ConcurrentDictionary<string, List<int>> _inboundSpeedRecords = new ConcurrentDictionary<string, List<int>>();
private readonly ConcurrentDictionary<string, List<int>> _outboundSpeedRecords = new ConcurrentDictionary<string, List<int>>();
private readonly ConcurrentDictionary<string, InOutBoundRecord> _inOutBoundRecords = new ConcurrentDictionary<string, InOutBoundRecord>();
private class InOutBoundRecord
{
private long _inbound;
private long _lastInbound;
private long _outbound;
private long _lastOutbound;
public void UpdateInbound(long delta)
{
Interlocked.Add(ref _inbound, delta);
}
public void UpdateOutbound(long delta)
{
Interlocked.Add(ref _outbound, delta);
}
public void GetDelta(out long inboundDelta, out long outboundDelta)
{
var i = Interlocked.Read(ref _inbound);
var il = Interlocked.Exchange(ref _lastInbound, i);
inboundDelta = i - il;
var o = Interlocked.Read(ref _outbound);
var ol = Interlocked.Exchange(ref _lastOutbound, o);
outboundDelta = o - ol;
}
}
//tasks
private readonly TimeSpan _delayBeforeStart = TimeSpan.FromSeconds(1);
private readonly TimeSpan _retryInterval = TimeSpan.FromMinutes(2);
private TimeSpan RecordingInterval => TimeSpan.FromMinutes(Config.DataCollectionMinutes);
private Timer _perSecondTimer; //analyze and save cached records to RawStatistics and filter records
private readonly TimeSpan _monitorInterval = TimeSpan.FromSeconds(1);
//private Timer _writer; //write RawStatistics to file
//private readonly TimeSpan _writingInterval = TimeSpan.FromMinutes(1);
private ShadowsocksController _controller;
private StatisticsStrategyConfiguration Config => _controller.StatisticsConfiguration;
// Static Singleton Initialization
public static AvailabilityStatistics Instance { get; } = new AvailabilityStatistics();
public Statistics RawStatistics { get; private set; }
public Statistics FilteredStatistics { get; private set; }
private AvailabilityStatistics()
{
RawStatistics = new Statistics();
}
internal void UpdateConfiguration(ShadowsocksController controller)
{
_controller = controller;
Reset();
try
{
if (Config.StatisticsEnabled)
{
LoadRawStatistics();
if (_perSecondTimer == null)
{
_perSecondTimer = new Timer(OperationsPerSecond, new Counter(), _delayBeforeStart, TimeSpan.FromSeconds(1));
}
}
else
{
_perSecondTimer?.Dispose();
}
}
catch (Exception e)
{
logger.LogUsefulException(e);
}
}
private void OperationsPerSecond(object state)
{
lock(state)
{
var counter = state as Counter;
if (counter.count % _monitorInterval.TotalSeconds == 0)
{
UpdateSpeed();
}
if (counter.count % RecordingInterval.TotalSeconds == 0)
{
Run();
}
counter.count++;
}
}
private void UpdateSpeed()
{
foreach (var kv in _inOutBoundRecords)
{
var id = kv.Key;
var record = kv.Value;
long inboundDelta, outboundDelta;
record.GetDelta(out inboundDelta, out outboundDelta);
var inboundSpeed = GetSpeedInKiBPerSecond(inboundDelta, _monitorInterval.TotalSeconds);
var outboundSpeed = GetSpeedInKiBPerSecond(outboundDelta, _monitorInterval.TotalSeconds);
// not thread safe
var inR = _inboundSpeedRecords.GetOrAdd(id, (k) => new List<int>());
var outR = _outboundSpeedRecords.GetOrAdd(id, (k) => new List<int>());
inR.Add(inboundSpeed);
outR.Add(outboundSpeed);
logger.Debug(
$"{id}: current/max inbound {inboundSpeed}/{inR.Max()} KiB/s, current/max outbound {outboundSpeed}/{outR.Max()} KiB/s");
}
}
private void Reset()
{
_inboundSpeedRecords.Clear();
_outboundSpeedRecords.Clear();
_latencyRecords.Clear();
}
private void Run()
{
UpdateRecords();
Reset();
}
private void UpdateRecords()
{
var records = new Dictionary<string, StatisticsRecord>();
UpdateRecordsState state = new UpdateRecordsState();
int serverCount = _controller.GetCurrentConfiguration().configs.Count;
state.counter = serverCount;
bool isPing = Config.Ping;
for (int i = 0; i < serverCount; i++)
{
try
{
var server = _controller.GetCurrentConfiguration().configs[i];
var id = server.Identifier();
List<int> inboundSpeedRecords = null;
List<int> outboundSpeedRecords = null;
List<int> latencyRecords = null;
_inboundSpeedRecords.TryGetValue(id, out inboundSpeedRecords);
_outboundSpeedRecords.TryGetValue(id, out outboundSpeedRecords);
_latencyRecords.TryGetValue(id, out latencyRecords);
StatisticsRecord record = new StatisticsRecord(id, inboundSpeedRecords, outboundSpeedRecords, latencyRecords);
/* duplicate server identifier */
if (records.ContainsKey(id))
records[id] = record;
else
records.Add(id, record);
if (isPing)
{
// FIXME: on ping completed, every thing could be asynchrously changed.
// focus on: Config/ RawStatistics
MyPing ping = new MyPing(server, Repeat);
ping.Completed += ping_Completed;
ping.Start(new PingState { state = state, record = record });
}
else if (!record.IsEmptyData())
{
AppendRecord(id, record);
}
}
catch
{
logger.Debug("config changed asynchrously, just ignore this server");
}
}
if (!isPing)
{
Save();
FilterRawStatistics();
}
}
private void ping_Completed(object sender, MyPing.CompletedEventArgs e)
{
PingState pingState = (PingState)e.UserState;
UpdateRecordsState state = pingState.state;
Server server = e.Server;
StatisticsRecord record = pingState.record;
record.SetResponse(e.RoundtripTime);
if (!record.IsEmptyData())
{
AppendRecord(server.Identifier(), record);
}
logger.Debug($"Ping {server} {e.RoundtripTime.Count} times, {(100 - record.PackageLoss * 100)}% packages loss, min {record.MinResponse} ms, max {record.MaxResponse} ms, avg {record.AverageResponse} ms");
if (Interlocked.Decrement(ref state.counter) == 0)
{
Save();
FilterRawStatistics();
}
}
private void AppendRecord(string serverIdentifier, StatisticsRecord record)
{
try
{
List<StatisticsRecord> records;
lock (RawStatistics)
{
if (!RawStatistics.TryGetValue(serverIdentifier, out records))
{
records = new List<StatisticsRecord>();
RawStatistics[serverIdentifier] = records;
}
}
records.Add(record);
}
catch (Exception e)
{
logger.LogUsefulException(e);
}
}
private void Save()
{
logger.Debug($"save statistics to {AvailabilityStatisticsFile}");
if (RawStatistics.Count == 0)
{
return;
}
try
{
string content;
#if DEBUG
content = JsonConvert.SerializeObject(RawStatistics, Formatting.Indented);
#else
content = JsonConvert.SerializeObject(RawStatistics, Formatting.None);
#endif
File.WriteAllText(AvailabilityStatisticsFile, content);
}
catch (IOException e)
{
logger.LogUsefulException(e);
}
}
private bool IsValidRecord(StatisticsRecord record)
{
if (Config.ByHourOfDay)
{
if (!record.Timestamp.Hour.Equals(DateTime.Now.Hour)) return false;
}
return true;
}
private void FilterRawStatistics()
{
try
{
logger.Debug("filter raw statistics");
if (RawStatistics == null) return;
var filteredStatistics = new Statistics();
foreach (var serverAndRecords in RawStatistics)
{
var server = serverAndRecords.Key;
var filteredRecords = serverAndRecords.Value.FindAll(IsValidRecord);
filteredStatistics[server] = filteredRecords;
}
FilteredStatistics = filteredStatistics;
}
catch (Exception e)
{
logger.LogUsefulException(e);
}
}
private void LoadRawStatistics()
{
try
{
var path = AvailabilityStatisticsFile;
logger.Debug($"loading statistics from {path}");
if (!File.Exists(path))
{
using (File.Create(path))
{
//do nothing
}
}
var content = File.ReadAllText(path);
RawStatistics = JsonConvert.DeserializeObject<Statistics>(content) ?? RawStatistics;
}
catch (Exception e)
{
logger.LogUsefulException(e);
Console.WriteLine($"failed to load statistics; use runtime statistics, some data may be lost");
}
}
private static int GetSpeedInKiBPerSecond(long bytes, double seconds)
{
var result = (int)(bytes / seconds) / 1024;
return result;
}
public void Dispose()
{
_perSecondTimer.Dispose();
}
public void UpdateLatency(Server server, int latency)
{
_latencyRecords.GetOrAdd(server.Identifier(), (k) =>
{
List<int> records = new List<int>();
records.Add(latency);
return records;
});
}
public void UpdateInboundCounter(Server server, long n)
{
_inOutBoundRecords.AddOrUpdate(server.Identifier(), (k) =>
{
var r = new InOutBoundRecord();
r.UpdateInbound(n);
return r;
}, (k, v) =>
{
v.UpdateInbound(n);
return v;
});
}
public void UpdateOutboundCounter(Server server, long n)
{
_inOutBoundRecords.AddOrUpdate(server.Identifier(), (k) =>
{
var r = new InOutBoundRecord();
r.UpdateOutbound(n);
return r;
}, (k, v) =>
{
v.UpdateOutbound(n);
return v;
});
}
private class Counter
{
public int count = 0;
}
class UpdateRecordsState
{
public int counter;
}
class PingState
{
public UpdateRecordsState state;
public StatisticsRecord record;
}
class MyPing
{
private static Logger logger = LogManager.GetCurrentClassLogger();
//arguments for ICMP tests
public const int TimeoutMilliseconds = 500;
public EventHandler<CompletedEventArgs> Completed;
private Server server;
private int repeat;
private IPAddress ip;
private Ping ping;
private List<int?> RoundtripTime;
public MyPing(Server server, int repeat)
{
this.server = server;
this.repeat = repeat;
RoundtripTime = new List<int?>(repeat);
ping = new Ping();
ping.PingCompleted += Ping_PingCompleted;
}
public void Start(object userstate)
{
if (server.server == "")
{
FireCompleted(new Exception("Invalid Server"), userstate);
return;
}
new Task(() => ICMPTest(0, userstate)).Start();
}
private void ICMPTest(int delay, object userstate)
{
try
{
logger.Debug($"Ping {server}");
if (ip == null)
{
ip = Dns.GetHostAddresses(server.server)
.First(
ip =>
ip.AddressFamily == AddressFamily.InterNetwork ||
ip.AddressFamily == AddressFamily.InterNetworkV6);
}
repeat--;
if (delay > 0)
Thread.Sleep(delay);
ping.SendAsync(ip, TimeoutMilliseconds, userstate);
}
catch (Exception e)
{
logger.Error($"An exception occured while eveluating {server}");
logger.LogUsefulException(e);
FireCompleted(e, userstate);
}
}
private void Ping_PingCompleted(object sender, PingCompletedEventArgs e)
{
try
{
if (e.Reply.Status == IPStatus.Success)
{
logger.Debug($"Ping {server} {e.Reply.RoundtripTime} ms");
RoundtripTime.Add((int?)e.Reply.RoundtripTime);
}
else
{
logger.Debug($"Ping {server} timeout");
RoundtripTime.Add(null);
}
TestNext(e.UserState);
}
catch (Exception ex)
{
logger.Error($"An exception occured while eveluating {server}");
logger.LogUsefulException(ex);
FireCompleted(ex, e.UserState);
}
}
private void TestNext(object userstate)
{
if (repeat > 0)
{
//Do ICMPTest in a random frequency
int delay = TimeoutMilliseconds + new Random().Next() % TimeoutMilliseconds;
new Task(() => ICMPTest(delay, userstate)).Start();
}
else
{
FireCompleted(null, userstate);
}
}
private void FireCompleted(Exception error, object userstate)
{
Completed?.Invoke(this, new CompletedEventArgs
{
Error = error,
Server = server,
RoundtripTime = RoundtripTime,
UserState = userstate
});
}
public class CompletedEventArgs : EventArgs
{
public Exception Error;
public Server Server;
public List<int?> RoundtripTime;
public object UserState;
}
}
}
}

+ 1
- 42
shadowsocks-csharp/Controller/ShadowsocksController.cs View File

@@ -40,9 +40,6 @@ namespace Shadowsocks.Controller
private PrivoxyRunner privoxyRunner; private PrivoxyRunner privoxyRunner;
private readonly ConcurrentDictionary<Server, Sip003Plugin> _pluginsByServer; private readonly ConcurrentDictionary<Server, Sip003Plugin> _pluginsByServer;
public AvailabilityStatistics availabilityStatistics = AvailabilityStatistics.Instance;
public StatisticsStrategyConfiguration StatisticsConfiguration { get; private set; }
private long _inboundCounter = 0; private long _inboundCounter = 0;
private long _outboundCounter = 0; private long _outboundCounter = 0;
public long InboundCounter => Interlocked.Read(ref _inboundCounter); public long InboundCounter => Interlocked.Read(ref _inboundCounter);
@@ -98,7 +95,6 @@ namespace Shadowsocks.Controller
httpClient = new HttpClient(); httpClient = new HttpClient();
_config = Configuration.Load(); _config = Configuration.Load();
Configuration.Process(ref _config); Configuration.Process(ref _config);
StatisticsConfiguration = StatisticsStrategyConfiguration.Load();
_strategyManager = new StrategyManager(this); _strategyManager = new StrategyManager(this);
_pluginsByServer = new ConcurrentDictionary<Server, Sip003Plugin>(); _pluginsByServer = new ConcurrentDictionary<Server, Sip003Plugin>();
StartTrafficStatistics(61); StartTrafficStatistics(61);
@@ -188,8 +184,6 @@ namespace Shadowsocks.Controller
httpClient.DefaultRequestHeaders.Add("User-Agent", _config.userAgentString); httpClient.DefaultRequestHeaders.Add("User-Agent", _config.userAgentString);
} }
StatisticsConfiguration = StatisticsStrategyConfiguration.Load();
privoxyRunner = privoxyRunner ?? new PrivoxyRunner(); privoxyRunner = privoxyRunner ?? new PrivoxyRunner();
_pacDaemon = _pacDaemon ?? new PACDaemon(_config); _pacDaemon = _pacDaemon ?? new PACDaemon(_config);
@@ -202,7 +196,6 @@ namespace Shadowsocks.Controller
GeositeUpdater.UpdateCompleted += PacServer_PACUpdateCompleted; GeositeUpdater.UpdateCompleted += PacServer_PACUpdateCompleted;
GeositeUpdater.Error += PacServer_PACUpdateError; GeositeUpdater.Error += PacServer_PACUpdateError;
availabilityStatistics.UpdateConfiguration(this);
_listener?.Stop(); _listener?.Stop();
StopPlugins(); StopPlugins();
@@ -220,7 +213,6 @@ namespace Shadowsocks.Controller
privoxyRunner.Start(_config); privoxyRunner.Start(_config);
TCPRelay tcpRelay = new TCPRelay(this, _config); TCPRelay tcpRelay = new TCPRelay(this, _config);
tcpRelay.OnConnected += UpdateLatency;
tcpRelay.OnInbound += UpdateInboundCounter; tcpRelay.OnInbound += UpdateInboundCounter;
tcpRelay.OnOutbound += UpdateOutboundCounter; tcpRelay.OnOutbound += UpdateOutboundCounter;
tcpRelay.OnFailed += (o, e) => GetCurrentStrategy()?.SetFailure(e.server); tcpRelay.OnFailed += (o, e) => GetCurrentStrategy()?.SetFailure(e.server);
@@ -524,7 +516,7 @@ namespace Shadowsocks.Controller
#endregion #endregion
#region Statistics
#region Strategy
public void SelectStrategy(string strategyID) public void SelectStrategy(string strategyID)
{ {
@@ -533,12 +525,6 @@ namespace Shadowsocks.Controller
SaveConfig(_config); SaveConfig(_config);
} }
public void SaveStrategyConfigurations(StatisticsStrategyConfiguration configuration)
{
StatisticsConfiguration = configuration;
StatisticsStrategyConfiguration.Save(configuration);
}
public IList<IStrategy> GetStrategies() public IList<IStrategy> GetStrategies()
{ {
return _strategyManager.GetStrategies(); return _strategyManager.GetStrategies();
@@ -556,43 +542,16 @@ namespace Shadowsocks.Controller
return null; return null;
} }
public void UpdateStatisticsConfiguration(bool enabled)
{
if (availabilityStatistics != null)
{
availabilityStatistics.UpdateConfiguration(this);
_config.availabilityStatistics = enabled;
SaveConfig(_config);
}
}
public void UpdateLatency(object sender, SSTCPConnectedEventArgs args)
{
GetCurrentStrategy()?.UpdateLatency(args.server, args.latency);
if (_config.availabilityStatistics)
{
availabilityStatistics.UpdateLatency(args.server, (int)args.latency.TotalMilliseconds);
}
}
public void UpdateInboundCounter(object sender, SSTransmitEventArgs args) public void UpdateInboundCounter(object sender, SSTransmitEventArgs args)
{ {
GetCurrentStrategy()?.UpdateLastRead(args.server); GetCurrentStrategy()?.UpdateLastRead(args.server);
Interlocked.Add(ref _inboundCounter, args.length); Interlocked.Add(ref _inboundCounter, args.length);
if (_config.availabilityStatistics)
{
availabilityStatistics.UpdateInboundCounter(args.server, args.length);
}
} }
public void UpdateOutboundCounter(object sender, SSTransmitEventArgs args) public void UpdateOutboundCounter(object sender, SSTransmitEventArgs args)
{ {
GetCurrentStrategy()?.UpdateLastWrite(args.server); GetCurrentStrategy()?.UpdateLastWrite(args.server);
Interlocked.Add(ref _outboundCounter, args.length); Interlocked.Add(ref _outboundCounter, args.length);
if (_config.availabilityStatistics)
{
availabilityStatistics.UpdateOutboundCounter(args.server, args.length);
}
} }
#endregion #endregion


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

@@ -1,170 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;

using Newtonsoft.Json;
using NLog;
using Shadowsocks.Model;

namespace Shadowsocks.Controller.Strategy
{
using Statistics = Dictionary<string, List<StatisticsRecord>>;

internal class StatisticsStrategy : IStrategy, IDisposable
{
private static Logger logger = LogManager.GetCurrentClassLogger();

private readonly ShadowsocksController _controller;
private Server _currentServer;
private readonly Timer _timer;
private Statistics _filteredStatistics;
private AvailabilityStatistics Service => _controller.availabilityStatistics;
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
// FIXME: consider Statistics and Config changing asynchrously.
_timer = new Timer(ReloadStatisticsAndChooseAServer);
}

private void ReloadStatisticsAndChooseAServer(object obj)
{
logger.Debug("Reloading statistics and choose a new server....");
var servers = _controller.GetCurrentConfiguration().configs;
LoadStatistics();
ChooseNewServer(servers);
}

private void LoadStatistics()
{
_filteredStatistics =
Service.FilteredStatistics ??
Service.RawStatistics ??
_filteredStatistics;
}

//return the score by data
//server with highest score will be choosen
private float? GetScore(string identifier, List<StatisticsRecord> records)
{
var config = _controller.StatisticsConfiguration;
float? score = null;

var averageRecord = new StatisticsRecord(identifier,
records.Where(record => record.MaxInboundSpeed != null).Select(record => record.MaxInboundSpeed.Value).ToList(),
records.Where(record => record.MaxOutboundSpeed != null).Select(record => record.MaxOutboundSpeed.Value).ToList(),
records.Where(record => record.AverageLatency != null).Select(record => record.AverageLatency.Value).ToList());
averageRecord.SetResponse(records.Select(record => record.AverageResponse).ToList());

foreach (var calculation in config.Calculations)
{
var name = calculation.Key;
var field = typeof (StatisticsRecord).GetField(name);
dynamic value = field?.GetValue(averageRecord);
var factor = calculation.Value;
if (value == null || factor.Equals(0)) continue;
score = score ?? 0;
score += value * factor;
}

if (score != null)
{
logger.Debug($"Highest score: {score} {JsonConvert.SerializeObject(averageRecord, Formatting.Indented)}");
}
return score;
}

private void ChooseNewServer(List<Server> servers)
{
if (_filteredStatistics == null || servers.Count == 0)
{
return;
}
try
{
var serversWithStatistics = (from server in servers
let id = server.Identifier()
where _filteredStatistics.ContainsKey(id)
let score = GetScore(id, _filteredStatistics[id])
where score != null
select new
{
server,
score
}).ToArray();

if (serversWithStatistics.Length < 2)
{
LogWhenEnabled("no enough statistics data or all factors in calculations are 0");
return;
}

var bestResult = serversWithStatistics
.Aggregate((server1, server2) => server1.score > server2.score ? server1 : server2);

LogWhenEnabled($"Switch to server: {bestResult.server.ToString()} by statistics: score {bestResult.score}");
_currentServer = bestResult.server;
}
catch (Exception e)
{
logger.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 statistics");

public Server GetAServer(IStrategyCallerType type, IPEndPoint localIPEndPoint, EndPoint destEndPoint)
{
if (_currentServer == null)
{
ChooseNewServer(_controller.GetCurrentConfiguration().configs);
}
return _currentServer; //current server cached for CachedInterval
}

public void ReloadServers()
{
ChooseNewServer(_controller.GetCurrentConfiguration().configs);
_timer?.Change(0, ChoiceKeptMilliseconds);
}

public void SetFailure(Server server)
{
logger.Debug($"failure: {server.ToString()}");
}

public void UpdateLastRead(Server server)
{
}

public void UpdateLastWrite(Server server)
{
}

public void UpdateLatency(Server server, TimeSpan latency)
{
}

public void Dispose()
{
_timer.Dispose();
}
}
}

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

@@ -13,7 +13,6 @@ 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 StatisticsStrategy(controller));
// TODO: load DLL plugins // TODO: load DLL plugins
} }
public IList<IStrategy> GetStrategies() public IList<IStrategy> GetStrategies()


+ 0
- 32
shadowsocks-csharp/Data/i18n.csv View File

@@ -16,7 +16,6 @@ PAC,Сценарий настройки (PAC),PAC 模式,PAC 模式,PACモード
Global,Для всей системы,全局模式,全局模式,グローバルプロキシ,전역,Global Global,Для всей системы,全局模式,全局模式,グローバルプロキシ,전역,Global
Servers,Серверы,服务器,伺服器,サーバー,서버,Serveurs Servers,Серверы,服务器,伺服器,サーバー,서버,Serveurs
Edit Servers...,Редактировать серверы…,编辑服务器...,編輯伺服器...,サーバーの編集...,서버 수정…,Éditer serveurs… Edit Servers...,Редактировать серверы…,编辑服务器...,編輯伺服器...,サーバーの編集...,서버 수정…,Éditer serveurs…
Statistics Config...,Настройки статистики…,统计配置...,統計設定檔...,統計情報の設定...,통계 설정,Configuration des statistiques…
Online Config...,,在线配置...,線上配置...,,, Online Config...,,在线配置...,線上配置...,,,
Start on Boot,Автозагрузка,开机启动,開機啟動,システム起動時に実行,시스템 시작 시에 시작하기,Démarrage automatique Start on Boot,Автозагрузка,开机启动,開機啟動,システム起動時に実行,시스템 시작 시에 시작하기,Démarrage automatique
Associate ss:// Links,Ассоциированный ss:// Ссылки,关联 ss:// 链接,關聯 ss:// 鏈接,ss:// リンクの関連付け,ss:// 링크 연결, Associate ss:// Links,Ассоциированный ss:// Ссылки,关联 ss:// 链接,關聯 ss:// 鏈接,ss:// リンクの関連付け,ss:// 링크 연결,
@@ -47,7 +46,6 @@ Quit,Выход,退出,結束,終了,종료,Quitter
Edit Servers,Редактирование серверов,编辑服务器,編輯伺服器,サーバーの編集,서버 수정,Éditer serveurs Edit Servers,Редактирование серверов,编辑服务器,編輯伺服器,サーバーの編集,서버 수정,Éditer serveurs
Load Balance,Балансировка нагрузки,负载均衡,負載平衡,サーバーロードバランス,로드밸런싱,Répartition de charge Load Balance,Балансировка нагрузки,负载均衡,負載平衡,サーバーロードバランス,로드밸런싱,Répartition de charge
High Availability,Высокая доступность,高可用,高可用性,高可用性,고가용성,Haute disponibilité High Availability,Высокая доступность,高可用,高可用性,高可用性,고가용성,Haute disponibilité
Choose by statistics,На основе статистики,根据统计,根據統計,統計で選ぶ,통계 기반,Choisissez par statistiques
Show Plugin Output,События плагинов в журнале,显示插件输出,,プラグインの出力情報を表示,플러그인 출력 보이기,Afficher la sortie du plugin Show Plugin Output,События плагинов в журнале,显示插件输出,,プラグインの出力情報を表示,플러그인 출력 보이기,Afficher la sortie du plugin
Write translation template,Создать шаблон для перевода,写入翻译模板,,翻訳テンプレートファイルを書き込む,번역 템플릿 쓰기,Écrire un modèle de traduction Write translation template,Создать шаблон для перевода,写入翻译模板,,翻訳テンプレートファイルを書き込む,번역 템플릿 쓰기,Écrire un modèle de traduction
,,,,,, ,,,,,,
@@ -81,36 +79,6 @@ Move D&own,Ниже,下移(&O),下移 (&O),下に移動 (&O),아래로 (&O),Desc
deprecated,Устаревшее,不推荐,不推薦,非推奨,더 이상 사용되지 않음,Obsolète deprecated,Устаревшее,不推荐,不推薦,非推奨,더 이상 사용되지 않음,Obsolète
"Encryption method {0} not exist, will replace with {1}",,加密方法{0}不存在,将使用{1}代替,,暗号化方式{0}が存在しません,{1}に置換します,{0} 암호화 방식이 존재하지 않으므로 {1}로 대체될 것입니다.,"Méthode de chiffrement {0} n'existe pas, sera remplacée par {1}" "Encryption method {0} not exist, will replace with {1}",,加密方法{0}不存在,将使用{1}代替,,暗号化方式{0}が存在しません,{1}に置換します,{0} 암호화 방식이 존재하지 않으므로 {1}로 대체될 것입니다.,"Méthode de chiffrement {0} n'existe pas, sera remplacée par {1}"
,,,,,, ,,,,,,
#Statistics Config,,,,,,
,,,,,,
Enable Statistics,Включить сбор статистики,启用统计,,統計を有効にする,통계 활성화,Activer statistiques
Ping Test,Проверка связи (Ping),Ping测试,,Ping測定,Ping 테스트,Test ping
packages everytime,пакета на проверку,个包/次,,パケット/回,시간 당 패킷,Packages à chaque fois
By hour of day,Ежечасно,按照每天的小时数统计,,毎日の時間数で統計,매 시간,Par heure du jour
Collect data per,Собирать данные за,收集数据每,,ごとにデータを収集,데이터 수집 기간,Recueillir des données par
Keep choice for,Хранить отбор данных за,保持选择每,,,설정 유지하기,Gardez le choix pour
minutes,мин.,分钟,,分,분,Minutes
Final Score:,Финальная оценка:,总分:,,,최종 점수:,Score final:
AverageLatency,СредЗадержка,平均延迟,,平均遅延時間,평균 지연시간,LatenceMoyenne
MinLatency,МинЗадержка,最小延迟,,最小遅延時間,최소 지연시간,MinLatence
MaxLatency,МаксЗадержка,最大延迟,,最大遅延時間,최대 지연시간,MaxLatence
AverageInboundSpeed,СредВходСкорость,平均入站速度,,平均インバウンド速度,평균 인바운드 속도,VitesseEntranteMoyenne
MinInboundSpeed,МинВходСкорость,最小入站速度,,最小インバウンド速度,최소 인바운드 속도,MinVitesseEntrante
MaxInboundSpeed,СредВходСкорость,最大入站速度,,最大インバウンド速度,최대 인바운드 속도,MaxVitesseEntrante
AverageOutboundSpeed,СредИсхСкорость,平均出站速度,,平均アウトバウンド速度,평균 아웃바운드 속도,VitesseSortanteMoyenne
MinOutboundSpeed,МинИсхСкорость,最小出站速度,,最小アウトバウンド速度,최소 아웃바운드 속도,MinVitesseSortante
MaxOutboundSpeed,МаксИсхСкорость,最大出站速度,,最大アウトバウンド速度,최대 아웃바운드 속도,MaxVitesseSortante
AverageResponse,СредВремяОтвета,平均响应速度,,平均レスポンス速度,평균 응답,RéponseMoyenne
MinResponse,МинВремяОтвета,最小响应速度,,最小レスポンス速度,최소 응답,MinRéponse
MaxResponse,МаксВремяОтвета,最大响应速度,,最大レスポンス速度,최대 응답,MaxRéponse
PackageLoss,ПотериПакетов,丢包率,,パケットロス率,패킷 손실,Perte de paquets
Speed,Скорость,速度,,速度,속도,Vitesse
Package Loss,Потери пакетов,丢包率,,パケットロス率,패킷 손실,Perte de paquets
Ping,Ping,网络延迟,,Ping,Ping,Ping
Chart Mode,График,图表模式,,図表モード,차트 모드,Mode graphique
24h,24ч,24小时,,24時間,24시간,24h
all,За все время,全部,,すべて,전체,tout
,,,,,,
# Log Form,,,,,, # Log Form,,,,,,
,,,,,, ,,,,,,
&File,Файл,文件(&F),檔案 (&F),ファイル (&F),파일 (&F),Fichier &File,Файл,文件(&F),檔案 (&F),ファイル (&F),파일 (&F),Fichier


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

@@ -37,7 +37,6 @@ namespace Shadowsocks.Model
public bool useOnlinePac; public bool useOnlinePac;
public bool secureLocalPac; // enable secret for PAC server public bool secureLocalPac; // enable secret for PAC server
public bool regeneratePacOnUpdate; // regenerate pac.txt on version update public bool regeneratePacOnUpdate; // regenerate pac.txt on version update
public bool availabilityStatistics;
public bool autoCheckUpdate; public bool autoCheckUpdate;
public bool checkPreRelease; public bool checkPreRelease;
public string skippedUpdateVersion; // skip the update with this version number public string skippedUpdateVersion; // skip the update with this version number
@@ -76,7 +75,6 @@ namespace Shadowsocks.Model
useOnlinePac = false; useOnlinePac = false;
secureLocalPac = true; secureLocalPac = true;
regeneratePacOnUpdate = true; regeneratePacOnUpdate = true;
availabilityStatistics = false;
autoCheckUpdate = false; autoCheckUpdate = false;
checkPreRelease = false; checkPreRelease = false;
skippedUpdateVersion = ""; skippedUpdateVersion = "";


+ 0
- 95
shadowsocks-csharp/Model/StatisticsRecord.cs View File

@@ -1,95 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Shadowsocks.Model
{
// Simple processed records for a short period of time
public class StatisticsRecord
{
public DateTime Timestamp { get; set; } = DateTime.Now;
public string ServerIdentifier { get; set; }

// in ping-only records, these fields would be null
public int? AverageLatency;
public int? MinLatency;
public int? MaxLatency;

private bool EmptyLatencyData => (AverageLatency == null) && (MinLatency == null) && (MaxLatency == null);

public int? AverageInboundSpeed;
public int? MinInboundSpeed;
public int? MaxInboundSpeed;

private bool EmptyInboundSpeedData
=> (AverageInboundSpeed == null) && (MinInboundSpeed == null) && (MaxInboundSpeed == null);

public int? AverageOutboundSpeed;
public int? MinOutboundSpeed;
public int? MaxOutboundSpeed;

private bool EmptyOutboundSpeedData
=> (AverageOutboundSpeed == null) && (MinOutboundSpeed == null) && (MaxOutboundSpeed == null);

// if user disabled ping test, response would be null
public int? AverageResponse;
public int? MinResponse;
public int? MaxResponse;
public float? PackageLoss;

private bool EmptyResponseData
=> (AverageResponse == null) && (MinResponse == null) && (MaxResponse == null) && (PackageLoss == null);

public bool IsEmptyData() {
return EmptyInboundSpeedData && EmptyOutboundSpeedData && EmptyResponseData && EmptyLatencyData;
}

public StatisticsRecord()
{
}

public StatisticsRecord(string identifier, ICollection<int> inboundSpeedRecords, ICollection<int> outboundSpeedRecords, ICollection<int> latencyRecords)
{
ServerIdentifier = identifier;
var inbound = inboundSpeedRecords?.Where(s => s > 0).ToList();
if (inbound != null && inbound.Any())
{
AverageInboundSpeed = (int) inbound.Average();
MinInboundSpeed = inbound.Min();
MaxInboundSpeed = inbound.Max();
}
var outbound = outboundSpeedRecords?.Where(s => s > 0).ToList();
if (outbound!= null && outbound.Any())
{
AverageOutboundSpeed = (int) outbound.Average();
MinOutboundSpeed = outbound.Min();
MaxOutboundSpeed = outbound.Max();
}
var latency = latencyRecords?.Where(s => s > 0).ToList();
if (latency!= null && latency.Any())
{
AverageLatency = (int) latency.Average();
MinLatency = latency.Min();
MaxLatency = latency.Max();
}
}

public StatisticsRecord(string identifier, ICollection<int?> responseRecords)
{
ServerIdentifier = identifier;
SetResponse(responseRecords);
}

public void SetResponse(ICollection<int?> responseRecords)
{
if (responseRecords == null) return;
var records = responseRecords.Where(response => response != null).Select(response => response.Value).ToList();
if (!records.Any()) return;
AverageResponse = (int?) records.Average();
MinResponse = records.Min();
MaxResponse = records.Max();
PackageLoss = responseRecords.Count(response => response != null)/(float) responseRecords.Count;
}
}
}

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

@@ -1,69 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;

using Newtonsoft.Json;
using NLog;

namespace Shadowsocks.Model
{
[Serializable]
public class StatisticsStrategyConfiguration
{
private static Logger logger = LogManager.GetCurrentClassLogger();

public static readonly string ID = "com.shadowsocks.strategy.statistics";
public bool StatisticsEnabled { get; set; } = false;
public bool ByHourOfDay { get; set; } = true;
public bool Ping { get; set; }
public int ChoiceKeptMinutes { get; set; } = 10;
public int DataCollectionMinutes { get; set; } = 10;
public int RepeatTimesNum { get; set; } = 4;

private const string ConfigFile = "statistics-config.json";

public static StatisticsStrategyConfiguration Load()
{
try
{
var content = File.ReadAllText(ConfigFile);
var configuration = JsonConvert.DeserializeObject<StatisticsStrategyConfiguration>(content);
return configuration;
}
catch (FileNotFoundException)
{
var configuration = new StatisticsStrategyConfiguration();
Save(configuration);
return configuration;
}
catch (Exception e)
{
logger.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)
{
logger.LogUsefulException(e);
}
}

public Dictionary<string, float> Calculations;

public StatisticsStrategyConfiguration()
{
var properties = typeof(StatisticsRecord).GetFields(BindingFlags.Instance | BindingFlags.Public);
Calculations = properties.ToDictionary(p => p.Name, _ => (float)0);
}
}
}

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

@@ -1,10 +0,0 @@
<?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
- 113
shadowsocks-csharp/View/CalculationControl.Designer.cs View File

@@ -1,113 +0,0 @@
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.Dock = System.Windows.Forms.DockStyle.Right;
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(236, 0);
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(202, 2);
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, 2);
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, 6);
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, 20F);
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.Margin = new System.Windows.Forms.Padding(0);
this.Name = "CalculationControl";
this.Size = new System.Drawing.Size(322, 34);
((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
- 25
shadowsocks-csharp/View/CalculationControl.cs View File

@@ -1,25 +0,0 @@
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
- 120
shadowsocks-csharp/View/CalculationControl.resx View File

@@ -1,120 +0,0 @@
<?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>

+ 0
- 7
shadowsocks-csharp/View/MenuViewController.cs View File

@@ -265,7 +265,6 @@ 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),
new MenuItem("-"), new MenuItem("-"),
CreateMenuItem("Share Server Config...", new EventHandler(this.QRCodeItem_Click)), CreateMenuItem("Share Server Config...", 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)),
@@ -725,12 +724,6 @@ namespace Shadowsocks.View
Process.Start(_urlToOpen); Process.Start(_urlToOpen);
} }
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)
{ {
if (serverSharingWindow == null) if (serverSharingWindow == null)


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

@@ -1,537 +0,0 @@
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();
System.Windows.Forms.DataVisualization.Charting.Series series3 = new System.Windows.Forms.DataVisualization.Charting.Series();
this.StatisticsChart = new System.Windows.Forms.DataVisualization.Charting.Chart();
this.PingCheckBox = new System.Windows.Forms.CheckBox();
this.KeepChoiceForLabel = new System.Windows.Forms.Label();
this.MinutesLabel2 = 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.CollectDataPerLabel = new System.Windows.Forms.Label();
this.MinutesLabel1 = 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.PackagePerPingLabel = new System.Windows.Forms.Label();
this.splitContainer3 = new System.Windows.Forms.SplitContainer();
this.FinalScoreLabel = 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.CalculatinTip = new System.Windows.Forms.ToolTip(this.components);
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.Enabled = System.Windows.Forms.DataVisualization.Charting.AxisEnabled.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.Color = System.Drawing.Color.DarkGray;
series1.Legend = "ChartLegend";
series1.Name = "Speed";
series1.ToolTip = "#VALX\\nMax inbound speed\\n#VAL KiB/s";
series1.XValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.Time;
series2.ChartArea = "DataArea";
series2.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Bubble;
series2.Color = System.Drawing.Color.Crimson;
series2.CustomProperties = "EmptyPointValue=Zero";
series2.Legend = "ChartLegend";
series2.Name = "Package Loss";
series2.ToolTip = "#VALX\\n#VAL%";
series2.XValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.Time;
series2.YAxisType = System.Windows.Forms.DataVisualization.Charting.AxisType.Secondary;
series2.YValuesPerPoint = 2;
series3.BorderWidth = 5;
series3.ChartArea = "DataArea";
series3.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Line;
series3.Color = System.Drawing.Color.DodgerBlue;
series3.Legend = "ChartLegend";
series3.MarkerSize = 10;
series3.MarkerStyle = System.Windows.Forms.DataVisualization.Charting.MarkerStyle.Circle;
series3.Name = "Ping";
series3.ToolTip = "#VALX\\n#VAL ms";
series3.XValueType = System.Windows.Forms.DataVisualization.Charting.ChartValueType.Time;
this.StatisticsChart.Series.Add(series1);
this.StatisticsChart.Series.Add(series2);
this.StatisticsChart.Series.Add(series3);
this.StatisticsChart.Size = new System.Drawing.Size(982, 435);
this.StatisticsChart.TabIndex = 2;
//
// PingCheckBox
//
this.PingCheckBox.AutoSize = true;
this.PingCheckBox.DataBindings.Add(new System.Windows.Forms.Binding("Checked", this.bindingConfiguration, "Ping", true));
this.PingCheckBox.Location = new System.Drawing.Point(13, 54);
this.PingCheckBox.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10);
this.PingCheckBox.Name = "PingCheckBox";
this.PingCheckBox.Size = new System.Drawing.Size(107, 27);
this.PingCheckBox.TabIndex = 5;
this.PingCheckBox.Text = "Ping Test";
this.PingCheckBox.UseVisualStyleBackColor = true;
this.PingCheckBox.CheckedChanged += new System.EventHandler(this.PingCheckBox_CheckedChanged);
//
// KeepChoiceForLabel
//
this.KeepChoiceForLabel.AutoSize = true;
this.KeepChoiceForLabel.Location = new System.Drawing.Point(9, 206);
this.KeepChoiceForLabel.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0);
this.KeepChoiceForLabel.Name = "KeepChoiceForLabel";
this.KeepChoiceForLabel.Size = new System.Drawing.Size(139, 23);
this.KeepChoiceForLabel.TabIndex = 8;
this.KeepChoiceForLabel.Text = "Keep choice for";
//
// MinutesLabel2
//
this.MinutesLabel2.AutoSize = true;
this.MinutesLabel2.Location = new System.Drawing.Point(286, 206);
this.MinutesLabel2.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0);
this.MinutesLabel2.Name = "MinutesLabel2";
this.MinutesLabel2.Size = new System.Drawing.Size(75, 23);
this.MinutesLabel2.TabIndex = 9;
this.MinutesLabel2.Text = "minutes";
//
// 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(733, 182);
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(234, 103);
this.chartModeSelector.TabIndex = 3;
this.chartModeSelector.TabStop = false;
this.chartModeSelector.Text = "Chart Mode";
//
// allMode
//
this.allMode.AutoSize = 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(50, 27);
this.allMode.TabIndex = 1;
this.allMode.Text = "all";
this.allMode.UseVisualStyleBackColor = true;
this.allMode.CheckedChanged += new System.EventHandler(this.allMode_CheckedChanged);
//
// dayMode
//
this.dayMode.AutoSize = true;
this.dayMode.Checked = 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(61, 27);
this.dayMode.TabIndex = 0;
this.dayMode.TabStop = true;
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(982, 753);
this.splitContainer1.SplitterDistance = 308;
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.CollectDataPerLabel);
this.splitContainer2.Panel1.Controls.Add(this.MinutesLabel1);
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.PackagePerPingLabel);
this.splitContainer2.Panel1.Controls.Add(this.KeepChoiceForLabel);
this.splitContainer2.Panel1.Controls.Add(this.PingCheckBox);
this.splitContainer2.Panel1.Controls.Add(this.MinutesLabel2);
//
// splitContainer2.Panel2
//
this.splitContainer2.Panel2.Controls.Add(this.splitContainer3);
this.splitContainer2.Size = new System.Drawing.Size(982, 308);
this.splitContainer2.SplitterDistance = 384;
this.splitContainer2.SplitterWidth = 5;
this.splitContainer2.TabIndex = 7;
//
// CollectDataPerLabel
//
this.CollectDataPerLabel.AutoSize = true;
this.CollectDataPerLabel.Location = new System.Drawing.Point(9, 164);
this.CollectDataPerLabel.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0);
this.CollectDataPerLabel.Name = "CollectDataPerLabel";
this.CollectDataPerLabel.Size = new System.Drawing.Size(139, 23);
this.CollectDataPerLabel.TabIndex = 20;
this.CollectDataPerLabel.Text = "Collect data per";
//
// MinutesLabel1
//
this.MinutesLabel1.AutoSize = true;
this.MinutesLabel1.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.MinutesLabel1.Location = new System.Drawing.Point(286, 165);
this.MinutesLabel1.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0);
this.MinutesLabel1.Name = "MinutesLabel1";
this.MinutesLabel1.Size = new System.Drawing.Size(75, 23);
this.MinutesLabel1.TabIndex = 19;
this.MinutesLabel1.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(177, 162);
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[] {
1,
0,
0,
0});
this.dataCollectionMinutesNum.Name = "dataCollectionMinutesNum";
this.dataCollectionMinutesNum.Size = new System.Drawing.Size(100, 29);
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(163, 27);
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(177, 204);
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[] {
1,
0,
0,
0});
this.choiceKeptMinutesNum.Name = "choiceKeptMinutesNum";
this.choiceKeptMinutesNum.Size = new System.Drawing.Size(100, 29);
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, 127);
this.byHourOfDayCheckBox.Margin = new System.Windows.Forms.Padding(5, 10, 5, 10);
this.byHourOfDayCheckBox.Name = "byHourOfDayCheckBox";
this.byHourOfDayCheckBox.Size = new System.Drawing.Size(150, 27);
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(34, 84);
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, 29);
this.repeatTimesNum.TabIndex = 14;
this.repeatTimesNum.Value = new decimal(new int[] {
4,
0,
0,
0});
//
// PackagePerPingLabel
//
this.PackagePerPingLabel.AutoSize = true;
this.PackagePerPingLabel.Font = new System.Drawing.Font("微软雅黑", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.PackagePerPingLabel.Location = new System.Drawing.Point(139, 86);
this.PackagePerPingLabel.Name = "PackagePerPingLabel";
this.PackagePerPingLabel.Size = new System.Drawing.Size(172, 23);
this.PackagePerPingLabel.TabIndex = 13;
this.PackagePerPingLabel.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.FinalScoreLabel);
//
// splitContainer3.Panel2
//
this.splitContainer3.Panel2.Controls.Add(this.calculationContainer);
this.splitContainer3.Size = new System.Drawing.Size(593, 308);
this.splitContainer3.SplitterDistance = 42;
this.splitContainer3.SplitterWidth = 1;
this.splitContainer3.TabIndex = 6;
//
// FinalScoreLabel
//
this.FinalScoreLabel.AutoSize = true;
this.FinalScoreLabel.Location = new System.Drawing.Point(5, 9);
this.FinalScoreLabel.Margin = new System.Windows.Forms.Padding(5, 0, 5, 0);
this.FinalScoreLabel.Name = "FinalScoreLabel";
this.FinalScoreLabel.Size = new System.Drawing.Size(103, 23);
this.FinalScoreLabel.TabIndex = 0;
this.FinalScoreLabel.Text = "Final Score:";
this.CalculatinTip.SetToolTip(this.FinalScoreLabel, "(The server with the highest score would be choosen)");
//
// 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(593, 265);
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(733, 145);
this.serverSelector.Name = "serverSelector";
this.serverSelector.Size = new System.Drawing.Size(233, 31);
this.serverSelector.TabIndex = 6;
this.serverSelector.SelectionChangeCommitted += new System.EventHandler(this.serverSelector_SelectionChangeCommitted);
//
// 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(865, 364);
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(758, 364);
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(10F, 23F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.AutoSize = true;
this.ClientSize = new System.Drawing.Size(982, 753);
this.Controls.Add(this.splitContainer1);
this.Font = new System.Drawing.Font("微软雅黑", 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(1000, 800);
this.Name = "StatisticsStrategyConfigurationForm";
this.Text = "StatisticsStrategyConfigurationForm";
this.Text = "Statistics configuration";
((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 PingCheckBox;
private System.Windows.Forms.Label KeepChoiceForLabel;
private System.Windows.Forms.Label MinutesLabel2;
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 FinalScoreLabel;
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 PackagePerPingLabel;
private System.Windows.Forms.CheckBox byHourOfDayCheckBox;
private System.Windows.Forms.NumericUpDown choiceKeptMinutesNum;
private System.Windows.Forms.CheckBox StatisticsEnabledCheckBox;
private System.Windows.Forms.Label CollectDataPerLabel;
private System.Windows.Forms.Label MinutesLabel1;
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;
private System.Windows.Forms.ToolTip CalculatinTip;
}
}

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

@@ -1,170 +0,0 @@
using System;
using System.Drawing;
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 Shadowsocks.Properties;

namespace Shadowsocks.View
{
public partial class StatisticsStrategyConfigurationForm : Form
{
private readonly ShadowsocksController _controller;
private StatisticsStrategyConfiguration _configuration;
private readonly DataTable _dataTable = new DataTable();
private List<string> _servers;
private readonly Series _speedSeries;
private readonly Series _packageLossSeries;
private readonly Series _pingSeries;

public StatisticsStrategyConfigurationForm(ShadowsocksController controller)
{
if (controller == null) return;
InitializeComponent();
Icon = Icon.FromHandle(Resources.ssw128.GetHicon());
_speedSeries = StatisticsChart.Series["Speed"];
_packageLossSeries = StatisticsChart.Series["Package Loss"];
_pingSeries = StatisticsChart.Series["Ping"];
_controller = controller;
_controller.ConfigChanged += (sender, args) => LoadConfiguration();
UpdateTexts();
LoadConfiguration();
Load += (sender, args) => InitData();
}

private void UpdateTexts()
{
I18N.TranslateForm(this);

foreach (var item in StatisticsChart.Series)
{
item.Name = I18N.GetString(item.Name);
}

}
private void LoadConfiguration()
{
var configs = _controller.GetCurrentConfiguration().configs;
_servers = configs.Select(server => server.Identifier()).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(I18N.GetString(kv.Key), kv.Value);
calculationContainer.Controls.Add(calculation);
}

serverSelector.DataSource = _servers;

_dataTable.Columns.Add("Timestamp", typeof(DateTime));
_dataTable.Columns.Add("Speed", typeof(int));
_speedSeries.XValueMember = "Timestamp";
_speedSeries.YValueMembers = "Speed";

// might be empty
_dataTable.Columns.Add("Package Loss", typeof(int));
_dataTable.Columns.Add("Ping", typeof(int));
_packageLossSeries.XValueMember = "Timestamp";
_packageLossSeries.YValueMembers = "Package Loss";
_pingSeries.XValueMember = "Timestamp";
_pingSeries.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()
{
var serverName = _servers[serverSelector.SelectedIndex];
_dataTable.Rows.Clear();

//return directly when no data is usable
if (_controller.availabilityStatistics?.FilteredStatistics == null) return;
List<StatisticsRecord> statistics;
if (!_controller.availabilityStatistics.FilteredStatistics.TryGetValue(serverName, out statistics)) return;
IEnumerable<IGrouping<int, StatisticsRecord>> dataGroups;
if (allMode.Checked)
{
_pingSeries.XValueType = ChartValueType.DateTime;
_packageLossSeries.XValueType = ChartValueType.DateTime;
_speedSeries.XValueType = ChartValueType.DateTime;
dataGroups = statistics.GroupBy(data => data.Timestamp.DayOfYear);
StatisticsChart.ChartAreas["DataArea"].AxisX.LabelStyle.Format = "g";
StatisticsChart.ChartAreas["DataArea"].AxisX2.LabelStyle.Format = "g";
}
else
{
_pingSeries.XValueType = ChartValueType.Time;
_packageLossSeries.XValueType = ChartValueType.Time;
_speedSeries.XValueType = ChartValueType.Time;
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
{
dataGroup.First().Timestamp,
Speed = dataGroup.Max(data => data.MaxInboundSpeed) ?? 0,
Ping = (int)(dataGroup.Average(data => data.AverageResponse) ?? 0),
PackageLossPercentage = (int)(dataGroup.Average(data => data.PackageLoss) ?? 0) * 100
};
foreach (var data in finalData.Where(data => data.Speed != 0 || data.PackageLossPercentage != 0 || data.Ping != 0))
{
_dataTable.Rows.Add(data.Timestamp, data.Speed, data.PackageLossPercentage, data.Ping);
}
StatisticsChart.DataBind();
}

private void serverSelector_SelectionChangeCommitted(object sender, EventArgs e)
{
LoadChartData();
}

private void dayMode_CheckedChanged(object sender, EventArgs e)
{
LoadChartData();
}

private void allMode_CheckedChanged(object sender, EventArgs e)
{
LoadChartData();
}

private void PingCheckBox_CheckedChanged(object sender, EventArgs e)
{
repeatTimesNum.ReadOnly = !PingCheckBox.Checked;
}
}
}

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

@@ -1,129 +0,0 @@
<?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>4, 5</value>
</metadata>
<metadata name="CalculatinTip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>238, 6</value>
</metadata>
<metadata name="$this.TrayHeight" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>191</value>
</metadata>
</root>

+ 0
- 23
shadowsocks-csharp/shadowsocks-csharp.csproj View File

@@ -268,9 +268,7 @@
<Compile Include="Proxy\DirectConnect.cs" /> <Compile Include="Proxy\DirectConnect.cs" />
<Compile Include="Proxy\HttpProxy.cs" /> <Compile Include="Proxy\HttpProxy.cs" />
<Compile Include="Proxy\IProxy.cs" /> <Compile Include="Proxy\IProxy.cs" />
<Compile Include="Controller\Service\AvailabilityStatistics.cs" />
<Compile Include="Controller\Strategy\HighAvailabilityStrategy.cs" /> <Compile Include="Controller\Strategy\HighAvailabilityStrategy.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\I18N.cs" /> <Compile Include="Controller\I18N.cs" />
@@ -282,8 +280,6 @@
<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\StatisticsRecord.cs" />
<Compile Include="Model\StatisticsStrategyConfiguration.cs" />
<Compile Include="Controller\Strategy\BalancingStrategy.cs" /> <Compile Include="Controller\Strategy\BalancingStrategy.cs" />
<Compile Include="Controller\Strategy\StrategyManager.cs" /> <Compile Include="Controller\Strategy\StrategyManager.cs" />
<Compile Include="Controller\Strategy\IStrategy.cs" /> <Compile Include="Controller\Strategy\IStrategy.cs" />
@@ -329,12 +325,6 @@
<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>
@@ -348,12 +338,6 @@
<Compile Include="Views\ServerSharingView.xaml.cs"> <Compile Include="Views\ServerSharingView.xaml.cs">
<DependentUpon>ServerSharingView.xaml</DependentUpon> <DependentUpon>ServerSharingView.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="View\StatisticsStrategyConfigurationForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="View\StatisticsStrategyConfigurationForm.Designer.cs">
<DependentUpon>StatisticsStrategyConfigurationForm.cs</DependentUpon>
</Compile>
<EmbeddedResource Include="Localization\Strings.fr.resx" /> <EmbeddedResource Include="Localization\Strings.fr.resx" />
<EmbeddedResource Include="Localization\Strings.ja.resx" /> <EmbeddedResource Include="Localization\Strings.ja.resx" />
<EmbeddedResource Include="Localization\Strings.ko.resx" /> <EmbeddedResource Include="Localization\Strings.ko.resx" />
@@ -373,15 +357,9 @@
<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\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>
@@ -397,7 +375,6 @@
<None Include="Data\sysproxy.exe.gz" /> <None Include="Data\sysproxy.exe.gz" />
<None Include="Data\sysproxy64.exe.gz" /> <None Include="Data\sysproxy64.exe.gz" />
<None Include="packages.config" /> <None Include="packages.config" />
<None Include="Properties\DataSources\Shadowsocks.Model.StatisticsStrategyConfiguration.datasource" />
<None Include="Properties\Settings.settings"> <None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator> <Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput> <LastGenOutput>Settings.Designer.cs</LastGenOutput>


Loading…
Cancel
Save