chr
2026-04-05 fe750b791d5b517cc4e9bc8e99a9a75139a0cfba
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
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
{
    /// <summary>
    /// A mechanism for retrieving <see cref="IPicture"/> data with <see cref="PictureDataExtensions"/>
    /// </summary>
    internal interface IPictureDataProvider
    {
        /// <summary>
        /// The order in which IPictureDataProviders will be tested. Lowers numbers go first
        /// </summary>
        double Order { get; } 
        /// <summary>
        /// Get a stream of the picture data
        /// </summary>
        /// <param name="picture"></param>
        /// <returns></returns>
        Task<Stream> GetStream(IPicture picture); 
        /// <summary>
        /// Get a string specifying the picture format
        /// </summary>
        /// <param name="picture"></param>
        /// <returns></returns>
        Task<string> GetFormat(IPicture picture);
    }
    
    /// <summary>
    /// Picture data provider for URI sources.
    /// </summary>
    class DefaultPictureDataProvider : IPictureDataProvider
    {
        public double Order => 10;
 
        /// <summary>
        /// Relative URIs are poorly supported in dotnet core. Ensure we only use absolute URIs by normalizing path strings to absolute paths.
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        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<Stream> 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<string> 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<string>(null);
        }
    }
    
 
    /// <summary>
    /// Provide <see cref="IPicture"/> data from <see cref="IPictureDataProvider"/> implementations.
    /// </summary>
    public static class PictureDataExtensions
    {
        private static IPictureDataProvider[] cache = Array.Empty<IPictureDataProvider>();
        private static ISet<ITypeData> cacheKey;
 
        private static IEnumerable<IPictureDataProvider> GetProviders()
        {
            var types = TypeData.GetDerivedTypes<IPictureDataProvider>()
                .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<IPictureDataProvider>().OrderBy(p => p.Order)
                    .ToArray();
                cacheKey = types;
            }
 
            return cache;
        }
 
        private static TraceSource log = Log.CreateSource(nameof(PictureDataExtensions));
        private static async Task<T> GetFirst<T>(IPicture picture, Func<IPicture, IPictureDataProvider, Task<T>> 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;
        }
 
 
        /// <summary>
        /// Get a stream of the picture from the first <see cref="IPictureDataProvider"/> which returns a non-null.
        /// </summary>
        /// <param name="picture"></param>
        public static Task<Stream> GetStream(this IPicture picture) =>
            GetFirst(picture, (pic, provider) => provider.GetStream(pic)); 
 
        /// <summary>
        /// Get the format of the picture from the first <see cref="IPictureDataProvider"/> which returns a non-null.
        /// </summary>
        /// <param name="picture"></param>
        public static Task<string> GetFormat(this IPicture picture) => GetFirst(picture, (pic, provider) => provider.GetFormat(pic));
    }
}