diff --git a/shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs b/shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs index 19cf64ad..a6ddc578 100644 --- a/shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs +++ b/shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs @@ -2,12 +2,20 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Net; +using SimpleJson; +using System.Net.Http; using System.Net.NetworkInformation; -using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; using Shadowsocks.Model; +using SimpleJson = SimpleJson.SimpleJson; +using Timer = System.Threading.Timer; namespace Shadowsocks.Controller { + using DataUnit = KeyValuePair; + using DataList = List>; class AvailabilityStatistics { private const string StatisticsFilesName = "shadowsocks.availability.csv"; @@ -51,35 +59,67 @@ namespace Shadowsocks.Controller } } - private void Evaluate(object obj) + //hardcode + //TODO: backup reliable isp&geolocation provider or a local database is required + private static async Task getGeolocationAndISP() { + Logging.Debug("Retrive information of geolocation and isp"); + const string api = "http://ip-api.com/json"; + var jsonString = await new HttpClient().GetStringAsync(api); + var ret = new DataList + { + new DataUnit(State.Geolocation, State.Unknown), + new DataUnit(State.ISP, State.Unknown), + }; + dynamic obj; + if (!global::SimpleJson.SimpleJson.TryDeserializeObject(jsonString, out obj)) return ret; + string country = obj["country"]; + string city = obj["city"]; + string isp = obj["isp"]; + string regionName= obj["regionName"]; + if (country == null || city == null || isp == null || regionName == null) return ret; + ret[0] = new DataUnit(State.Geolocation, $"{country} {regionName} {city}"); + ret[1] = new DataUnit(State.ISP, isp); + return ret; + } + + private static async Task> ICMPTest(Server server) + { + Logging.Debug("eveluating " + server.FriendlyName()); var ping = new Ping(); - var state = (State) obj; - foreach (var server in _servers) + var ret = new List(); + foreach (var timestamp in Enumerable.Range(0, Repeat).Select(_ => DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"))) + { + //ICMP echo. we can also set options and special bytes + var reply = await ping.SendTaskAsync(server.server, Timeout); + ret.Add(new List> + { + new KeyValuePair("Timestamp", timestamp), + new KeyValuePair("Server", server.FriendlyName()), + new KeyValuePair("Status", reply?.Status.ToString()), + new KeyValuePair("RoundtripTime", reply?.RoundtripTime.ToString()) + //new KeyValuePair("data", reply.Buffer.ToString()); // The data of reply + }); + } + return ret; + } + + private async void Evaluate(object obj) + { + var geolocationAndIsp = getGeolocationAndISP(); + foreach (var dataLists in await TaskEx.WhenAll(_servers.Select(ICMPTest))) { - Logging.Debug("eveluating " + server.FriendlyName()); - foreach (var _ in Enumerable.Range(0, Repeat)) + await geolocationAndIsp; + foreach (var dataList in dataLists) { - //TODO: do simple analyze of data to provide friendly message, like package loss. - var 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: - var reply = ping.Send(server.server, Timeout); - state.Data = new List> - { - new KeyValuePair("Timestamp", timestamp), - new KeyValuePair("Server", server.FriendlyName()), - new KeyValuePair("Status", reply?.Status.ToString()), - new KeyValuePair("RoundtripTime", reply?.RoundtripTime.ToString()) - }; - //state.data.Add(new KeyValuePair("data", reply.Buffer.ToString())); // The data of reply - Append(state.Data); + Append(dataList, geolocationAndIsp.Result); } } } - private static void Append(List> data) + private static void Append(DataList dataList, IEnumerable extra) { + var data = dataList.Concat(extra); var dataLine = string.Join(Delimiter, data.Select(kv => kv.Value).ToArray()); string[] lines; if (!File.Exists(AvailabilityStatisticsFile)) @@ -102,7 +142,10 @@ namespace Shadowsocks.Controller private class State { - public List> Data = new List>(); + public DataList dataList = new DataList(); + public const string Geolocation = "Geolocation"; + public const string ISP = "ISP"; + public const string Unknown = "Unknown"; } } } diff --git a/shadowsocks-csharp/app.config b/shadowsocks-csharp/app.config index 867ff468..a7ba1069 100755 --- a/shadowsocks-csharp/app.config +++ b/shadowsocks-csharp/app.config @@ -1,6 +1,19 @@ - + - - - + + + + + + + + + + + + + + + + diff --git a/shadowsocks-csharp/packages.config b/shadowsocks-csharp/packages.config new file mode 100644 index 00000000..2dd04b78 --- /dev/null +++ b/shadowsocks-csharp/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/shadowsocks-csharp/shadowsocks-csharp.csproj b/shadowsocks-csharp/shadowsocks-csharp.csproj index 200393d3..832a20c8 100644 --- a/shadowsocks-csharp/shadowsocks-csharp.csproj +++ b/shadowsocks-csharp/shadowsocks-csharp.csproj @@ -62,12 +62,39 @@ app.manifest + + + 3rd\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll + True + + + 3rd\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll + True + + + 3rd\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll + True + + + 3rd\Microsoft.Bcl.1.1.8\lib\net40\System.IO.dll + True + + + + + 3rd\Microsoft.Bcl.1.1.8\lib\net40\System.Runtime.dll + True + + + 3rd\Microsoft.Bcl.1.1.8\lib\net40\System.Threading.Tasks.dll + True + @@ -225,6 +252,7 @@ + @@ -267,6 +295,11 @@ + + + + +