///////////////////////////////////////////////////////////////////////////
// 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);
}
}
}
}