/////////////////////////////////////////////////////////////////////////// // Copyright (C) Wizardry and Steamworks 2017 - License: GNU GPLv3 // // Please see: http://www.gnu.org/licenses/gpl.html for legal details, // // rights of fair usage, the disclaimer and warranty conditions. // /////////////////////////////////////////////////////////////////////////// using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using MaxMind.GeoIP2.Responses; using wasDAVClient; using wasSharp.Geo; using wasStitchNET.GeoIP; using wasStitchNET.Structures; namespace wasStitchNET.Repository { public static class Mirrors { /// /// Returns a list of valid Stitch mirrors. /// /// the local city from where the method is invoked /// the mirror to get the distance to /// the was DAV client to use /// the Stitch server to query /// a list of discovered Stitch mirrors public static IEnumerable InitializeMirrors(CityResponse localCity, Client client, string server) { Func computeMirrorDistance = o => { var mirrorDistance = GetMirrorDistance(localCity, o); if (mirrorDistance.Equals(default(KeyValuePair))) return default(StitchMirror); return new StitchMirror(mirrorDistance.Key, mirrorDistance.Value); }; var mirrors = new List {server}; yield return computeMirrorDistance(server); foreach (var stitchMirror in mirrors.AsParallel().SelectMany( mirror => FetchStitchMirrors(client, mirror) .AsParallel() .Where(o => !string.IsNullOrEmpty(o) && !mirrors.Contains(o)) .Select(computeMirrorDistance))) { if (stitchMirror.Equals(default(StitchMirror))) continue; mirrors.Add(stitchMirror.Address); yield return stitchMirror; } } /// /// Retrieves a list of mirrors from a Stitch repository. /// /// the was DAV client to use /// the Stitch server to retrieve the mirrors from /// a list of mirrors public static IEnumerable FetchStitchMirrors(Client client, string server) { // Set the server. client.Server = server; // Set the mirror path var mirrorsPath = @"/" + string.Join(@"/", STITCH_CONSTANTS.UPDATE_PATH, STITCH_CONSTANTS.PROGRESSIVE_PATH, STITCH_CONSTANTS.UPDATE_MIRRORS_FILE); using (var stream = client.Download(mirrorsPath).Result) { if (stream == null) yield break; using (var streamReader = new StreamReader(stream)) { string mirror; do { mirror = streamReader.ReadLine(); if (string.IsNullOrEmpty(mirror)) continue; yield return mirror; } while (!string.IsNullOrEmpty(mirror)); } } } /// /// Gets the distance to a mirror. /// /// the local city from where the method is invoked /// the mirror to get the distance to /// a key-value pair of mirror by distance public static KeyValuePair GetMirrorDistance(CityResponse localCity, string mirror) { // Check that the mirror has a proper URI. Uri mirrorUri; if (!Uri.TryCreate(mirror, UriKind.Absolute, out mirrorUri)) return default(KeyValuePair); // If we do not know the local city, then just return the mirror. if (localCity == null) return new KeyValuePair(mirror, null); // Resolve the mirror hostname to an IP address. IPAddress address; try { address = Dns.GetHostAddresses(mirrorUri.Host).FirstOrDefault(); } catch (Exception) { return new KeyValuePair(mirror, null); } // Resolve the IP address to a city response. var remoteCity = address.GeoIPGetCity(); if (remoteCity == null) return new KeyValuePair(mirror, null); // Compute the distance to the mirror. switch (remoteCity.Location.HasCoordinates) { case true: var local = new GeographicCoordinate(localCity.Location.Latitude.Value, localCity.Location.Longitude.Value); var remote = new GeographicCoordinate(remoteCity.Location.Latitude.Value, remoteCity.Location.Longitude.Value); return new KeyValuePair(mirror, local.HaversineDistanceTo(remote)); default: return new KeyValuePair(mirror, null); } } } }