-
Notifications
You must be signed in to change notification settings - Fork 19
/
m3u8.client.factory.cs
389 lines (349 loc) · 13.2 KB
/
m3u8.client.factory.cs
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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
#if NETCOREAPP
using System.Net.Security;
using System.Security.Authentication;
#endif
using M = System.Runtime.CompilerServices.MethodImplAttribute;
using O = System.Runtime.CompilerServices.MethodImplOptions;
namespace m3u8.infrastructure
{
/// <summary>
///
/// </summary>
internal sealed class ICollectionDebugView< T >
{
private ICollection< T > _Collection;
public ICollectionDebugView( ICollection< T > collection ) => _Collection = collection ?? throw new ArgumentNullException( nameof(collection) );
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public T[] Items
{
get
{
var items = new T[ _Collection.Count ];
_Collection.CopyTo( items, 0 );
return (items);
}
}
}
/// <summary>
/// LRU (least recently used) cache
/// </summary>
internal interface ILRUCache< T > : ICollection< T >, IReadOnlyCollection< T >
{
int Limit { get; set; }
int Count_2 { get; }
bool TryGetValue( T equalValue, out T actualValue );
}
/// <summary>
///
/// </summary>
[DebuggerTypeProxy( typeof(ICollectionDebugView<>) )]
[DebuggerDisplay("Count = {Count}")]
internal sealed class LRUCache< T > : ILRUCache< T >, ICollection< T >, IReadOnlyCollection< T >
{
/// <summary>
///
/// </summary>
private sealed class LinkedListNode_EqualityComparer : IEqualityComparer< LinkedListNode< T > >
{
private IEqualityComparer< T > _Comparer;
public LinkedListNode_EqualityComparer( IEqualityComparer< T > comparer ) => _Comparer = comparer ?? EqualityComparer< T >.Default;
public bool Equals( LinkedListNode< T > x, LinkedListNode< T > y ) => _Comparer.Equals( x.Value, y.Value );
public int GetHashCode( LinkedListNode< T > obj ) => _Comparer.GetHashCode( obj.Value );
}
#region [.fields.]
private HashSet< LinkedListNode< T > > _HashSet;
private LinkedList< T > _LinkedList;
private int _Limit;
#endregion
#region [.ctor().]
public LRUCache( int limit, IEqualityComparer< T > comparer = null )
{
this.Limit = limit;
_HashSet = new HashSet< LinkedListNode< T > >( limit, new LinkedListNode_EqualityComparer( comparer ) );
_LinkedList = new LinkedList< T >();
}
private LRUCache() { }
public static LRUCache< T > CreateWithLimitMaxValue( int capacity, IEqualityComparer< T > comparer = null )
=> new LRUCache< T >()
{
Limit = int.MaxValue,
_HashSet = new HashSet< LinkedListNode< T > >( capacity, new LinkedListNode_EqualityComparer( comparer ) ),
_LinkedList = new LinkedList< T >()
};
#endregion
public int Limit
{
get => _Limit;
set
{
if ( value <= 0 ) throw (new ArgumentException( nameof(Limit) ));
_Limit = value;
}
}
public bool TryGetValue( T equalValue, out T actualValue )
{
if ( _HashSet.TryGetValue( ToNode( equalValue ), out var node ) )
{
MoveToFirst( node );
actualValue = node.Value;
return (true);
}
actualValue = default;
return (false);
}
[M(O.AggressiveInlining)] private static LinkedListNode< T > ToNode( T item ) => new LinkedListNode< T >( item );
[M(O.AggressiveInlining)] private void AddFirst( LinkedListNode< T > node )
{
_LinkedList.AddFirst( node );
_HashSet .Add( node );
}
[M(O.AggressiveInlining)] private void Remove( LinkedListNode< T > node )
{
_LinkedList.Remove( node );
_HashSet .Remove( node );
}
[M(O.AggressiveInlining)] private void MoveToFirst( LinkedListNode< T > node )
{
_LinkedList.Remove ( node );
_LinkedList.AddFirst( node );
}
public int Count => _LinkedList.Count;
public int Count_2 => _LinkedList.Count;
public bool IsReadOnly => false;
public void Add( T item )
{
var temp = ToNode( item );
if ( _HashSet.TryGetValue( temp, out var existsNode ) )
{
Remove( existsNode );
AddFirst( temp );
}
else
{
AddFirst( temp );
if ( _Limit < _HashSet.Count )
{
Remove( _LinkedList.Last );
}
}
}
public bool Remove( T item )
{
var temp = ToNode( item );
if ( _HashSet.TryGetValue( temp, out var existsNode ) )
{
Remove( existsNode );
return (true);
}
return (false);
}
public bool Contains( T item ) => _HashSet.Contains( ToNode( item ) );
public void Clear()
{
_HashSet .Clear();
_LinkedList.Clear();
}
public IEnumerator< T > GetEnumerator()
{
foreach ( var t in _LinkedList )
{
yield return (t);
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public void CopyTo( T[] array, int arrayIndex )
{
foreach ( var t in _LinkedList )
{
array[ arrayIndex++ ] = t;
}
}
#if DEBUG
public override string ToString() => $"Count: {Count}";
#endif
}
//--------------------------------------------------//
/// <summary>
///
/// </summary>
internal static class HttpClientFactory_WithRefCount
{
/// <summary>
///
/// </summary>
public struct init_params
{
public TimeSpan? Timeout { get; set; }
}
/// <summary>
///
/// </summary>
private sealed class tuple_t
{
/// <summary>
///
/// </summary>
public struct EqualityComparer : IEqualityComparer< tuple_t >
{
public bool Equals( tuple_t x, tuple_t y ) => (x.Timeout == y.Timeout);
public int GetHashCode( tuple_t obj ) => obj.Timeout.GetHashCode();
}
private tuple_t( TimeSpan timeout ) => Timeout = timeout;
public tuple_t( TimeSpan timeout, HttpClient httpClient ) => (Timeout, HttpClient, RefCount) = (timeout, httpClient, 0);
public TimeSpan Timeout { get; }
public HttpClient HttpClient { get; }
public int RefCount { get; private set; }
public int IncrementRefCount() => ++RefCount;
public int DecrementRefCount() => --RefCount;
public static tuple_t key( TimeSpan? timeout ) => new tuple_t( timeout.GetValueOrDefault( TimeSpan.Zero ) );
#if DEBUG
public override string ToString() => $"{Timeout}, (ref: {RefCount})";
#endif
}
private static ILRUCache< tuple_t > _LRUCache;
static HttpClientFactory_WithRefCount() => _LRUCache = LRUCache< tuple_t >.CreateWithLimitMaxValue( 0x10, new tuple_t.EqualityComparer() );
/// <summary>
///
/// </summary>
private struct free_tuple : IDisposable
{
private tuple_t _tuple;
public free_tuple( tuple_t t ) => _tuple = t ?? throw (new ArgumentNullException( nameof(t) ));
public void Dispose()
{
if ( _tuple != null )
{
HttpClientFactory_WithRefCount.Release( _tuple );
_tuple = null;
}
}
}
private static HttpClient CreateHttpClient( in TimeSpan? timeout )
{
#if NETCOREAPP
SocketsHttpHandler CreateSocketsHttpHandler( in TimeSpan? _timeout )
{
static void set_Protocol( SslClientAuthenticationOptions sslOptions, SslProtocols protocol )
{
try
{
sslOptions.EnabledSslProtocols |= protocol;
}
catch ( Exception ex )
{
Debug.WriteLine( ex );
}
};
var h = new SocketsHttpHandler();
h.SslOptions.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;
//set_Protocol( h.SslOptions, SslProtocols.Tls );
//set_Protocol( h.SslOptions, SslProtocols.Tls11 );
set_Protocol( h.SslOptions, SslProtocols.Tls12 );
#pragma warning disable CS0618
set_Protocol( h.SslOptions, SslProtocols.Ssl2 );
set_Protocol( h.SslOptions, SslProtocols.Ssl3 );
#pragma warning restore CS0618
if ( _timeout.HasValue )
{
h.ConnectTimeout = _timeout.Value;
}
return (h);
};
var handler = CreateSocketsHttpHandler( in timeout );
var httpClient = new HttpClient( handler, true );
#else
var httpClient = new HttpClient();
#endif
if ( timeout.HasValue )
{
httpClient.Timeout = timeout.Value;
}
return (httpClient);
}
public static (HttpClient, IDisposable) Get( in init_params ip ) => Get( ip.Timeout );
public static (HttpClient, IDisposable) Get( in TimeSpan? timeout = null )
{
var key = tuple_t.key( timeout );
lock ( _LRUCache )
{
if ( !_LRUCache.TryGetValue( key, out var t ) )
{
t = new tuple_t( key.Timeout, CreateHttpClient( in timeout ) );
_LRUCache.Add( t );
}
t.IncrementRefCount();
//-----------------------------------------------//
var removed = (from et in _LRUCache.Skip( 1 )
where (et.RefCount <= 0)
select et
).ToArray();
foreach ( var et in removed )
{
_LRUCache.Remove( et );
et.HttpClient.Dispose();
}
//-----------------------------------------------//
return (t.HttpClient, new free_tuple( t ));
}
}
private static void Release( tuple_t t )
{
lock ( _LRUCache )
{
var refCount = t.DecrementRefCount();
if ( (refCount <= 0) && (1 < _LRUCache.Count_2) )
{
var first_t = _LRUCache.First();
if ( t != first_t )
{
_LRUCache.Remove( t );
t.HttpClient.Dispose();
}
}
}
}
public static void ForceClearAndDisposeAll()
{
lock ( _LRUCache )
{
foreach ( var t in _LRUCache )
{
t.HttpClient.Dispose();
}
_LRUCache.Clear();
}
}
#if M3U8_CLIENT_TESTS
public static bool Any() => _LRUCache.Any();
public static (TimeSpan timeout, HttpClient hc, int refCount) GetTop()
{
var t = _LRUCache.First();
return (t.Timeout, t.HttpClient, t.RefCount);
}
#endif
}
}
namespace m3u8
{
using m3u8.infrastructure;
/// <summary>
///
/// </summary>
public static class m3u8_client_factory
{
public static m3u8_client Create() => Create( HttpClientFactory_WithRefCount.Get() );
public static m3u8_client Create( in (TimeSpan timeout, int attemptRequestCountByPart) t ) => Create( t.timeout, t.attemptRequestCountByPart );
public static m3u8_client Create( in TimeSpan timeout, int attemptRequestCountByPart = 10 ) => Create( HttpClientFactory_WithRefCount.Get( timeout ), attemptRequestCountByPart );
public static m3u8_client Create( in m3u8_client.init_params ip ) => Create( HttpClientFactory_WithRefCount.Get(), ip );
private static m3u8_client Create( in (HttpClient httpClient, IDisposable) t, in m3u8_client.init_params ip ) => new m3u8_client( t, ip );
private static m3u8_client Create( in (HttpClient httpClient, IDisposable) t, int attemptRequestCountByPart = 10 )
=> Create( t, new m3u8_client.init_params() { AttemptRequestCount = Math.Max( attemptRequestCountByPart, 1 ) } );
public static void ForceClearAndDisposeAll() => HttpClientFactory_WithRefCount.ForceClearAndDisposeAll();
}
}