using System; using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Threading.Tasks; namespace OpenTap { /// /// A mechanism for retrieving data with /// internal interface IPictureDataProvider { /// /// The order in which IPictureDataProviders will be tested. Lowers numbers go first /// double Order { get; } /// /// Get a stream of the picture data /// /// /// Task GetStream(IPicture picture); /// /// Get a string specifying the picture format /// /// /// Task GetFormat(IPicture picture); } /// /// Picture data provider for URI sources. /// class DefaultPictureDataProvider : IPictureDataProvider { public double Order => 10; /// /// Relative URIs are poorly supported in dotnet core. Ensure we only use absolute URIs by normalizing path strings to absolute paths. /// /// /// private string normalizeSource(string source) { try { if (File.Exists(source)) return Path.GetFullPath(source); } catch { // this is fine -- source is not a file } return source; } public async Task GetStream(IPicture picture) { if (await GetFormat(picture) == null) return null; var source = normalizeSource(picture.Source); if (Uri.TryCreate(source, UriKind.Absolute, out var uri)) { #pragma warning disable SYSLIB0014 var req = WebRequest.Create(uri); #pragma warning restore SYSLIB0014 var response = await req.GetResponseAsync(); return response.GetResponseStream(); } return null; } public Task GetFormat(IPicture picture) { var source = normalizeSource(picture.Source); if (Uri.TryCreate(source, UriKind.Absolute, out var uri)) { var name = uri.Segments.LastOrDefault(); if (string.IsNullOrWhiteSpace(name) == false) { var ext = Path.GetExtension(name); if (ext.StartsWith(".") && ext.Length > 1) return Task.FromResult(ext.Substring(1)); } } return Task.FromResult(null); } } /// /// Provide data from implementations. /// public static class PictureDataExtensions { private static IPictureDataProvider[] cache = Array.Empty(); private static ISet cacheKey; private static IEnumerable GetProviders() { var types = TypeData.GetDerivedTypes() .Where(td => td.CanCreateInstance).ToImmutableHashSet(); if (cacheKey == null || types.SetEquals(cacheKey) == false) { cache = types.TrySelect(td => td.CreateInstance(), ex => log.Debug("Unable to load IPictureDataProvider: {0}", ex.Message)) .OfType().OrderBy(p => p.Order) .ToArray(); cacheKey = types; } return cache; } private static TraceSource log = Log.CreateSource(nameof(PictureDataExtensions)); private static async Task GetFirst(IPicture picture, Func> func) where T : class { foreach (var provider in GetProviders()) { try { var res = await func(picture, provider); if (res != null) return res; } catch (Exception ex) { log.Debug($"Unexpected error in {nameof(IPictureDataProvider)} '{TypeData.GetTypeData(provider).AsTypeData().GetBestName()}'."); log.Debug(ex); } } return null; } /// /// Get a stream of the picture from the first which returns a non-null. /// /// public static Task GetStream(this IPicture picture) => GetFirst(picture, (pic, provider) => provider.GetStream(pic)); /// /// Get the format of the picture from the first which returns a non-null. /// /// public static Task GetFormat(this IPicture picture) => GetFirst(picture, (pic, provider) => provider.GetFormat(pic)); } }