build: repack to one dll assembly

This commit is contained in:
cxfksword
2023-11-17 14:37:56 +08:00
parent a9d2cc2de0
commit fb8cb609b1
28 changed files with 32 additions and 1266 deletions

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="ILRepacker" AfterTargets="Build" Condition="'$(Configuration)'=='Release' or '$(Configuration)'=='Debug'">
<PropertyGroup>
<DoILRepack>false</DoILRepack>
</PropertyGroup>
<ItemGroup>
<InputAssemblies Include="$(OutputPath)$(AssemblyName).dll" />
<InputAssemblies Include="$(OutputPath)RateLimiter.dll" />
<InputAssemblies Include="$(OutputPath)ComposableAsync.Core.dll" />
<InputAssemblies Include="$(OutputPath)Google.Protobuf.dll" />
<InputAssemblies Include="$(OutputPath)ICSharpCode.SharpZipLib.dll" />
</ItemGroup>
<ILRepack
Parallel="false"
Internalize="true"
DebugInfo="true"
InputAssemblies="@(InputAssemblies)"
LibraryPath="$(OutputPath)"
TargetKind="Dll"
OutputFile="$(OutputPath)$(AssemblyName).dll"
/>
</Target>
</Project>

View File

@@ -6,6 +6,7 @@
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<TreatWarningsAsErrors>False</TreatWarningsAsErrors>
@@ -14,9 +15,12 @@
<TreatWarningsAsErrors>False</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ComposableAsync.Core" Version="1.3.0" />
<PackageReference Include="Google.Protobuf" Version="3.22.0" />
<PackageReference Include="ILRepack.Lib.MSBuild.Minor" Version="2.1.19-alpha.2" />
<PackageReference Include="Jellyfin.Controller" Version="10.8.0" />
<PackageReference Include="Jellyfin.Model" Version="10.8.0" />
<PackageReference Include="RateLimiter" Version="2.2.0" />
<PackageReference Include="SharpZipLib" Version="1.4.2" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,4 +0,0 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("ComposableAsync.Core.Test")]

View File

@@ -1,43 +0,0 @@
using System;
using System.Runtime.CompilerServices;
using System.Security;
namespace ComposableAsync
{
/// <summary>
/// Dispatcher awaiter, making a dispatcher awaitable
/// </summary>
public struct DispatcherAwaiter : INotifyCompletion
{
/// <summary>
/// Dispatcher never is synchronous
/// </summary>
public bool IsCompleted => false;
private readonly IDispatcher _Dispatcher;
/// <summary>
/// Construct a NotifyCompletion fom a dispatcher
/// </summary>
/// <param name="dispatcher"></param>
public DispatcherAwaiter(IDispatcher dispatcher)
{
_Dispatcher = dispatcher;
}
/// <summary>
/// Dispatch on complete
/// </summary>
/// <param name="continuation"></param>
[SecuritySafeCritical]
public void OnCompleted(Action continuation)
{
_Dispatcher.Dispatch(continuation);
}
/// <summary>
/// No Result
/// </summary>
public void GetResult() { }
}
}

View File

@@ -1,30 +0,0 @@
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace ComposableAsync
{
/// <summary>
/// A <see cref="DelegatingHandler"/> implementation based on <see cref="IDispatcher"/>
/// </summary>
internal class DispatcherDelegatingHandler : DelegatingHandler
{
private readonly IDispatcher _Dispatcher;
/// <summary>
/// Build an <see cref="DelegatingHandler"/> from a <see cref="IDispatcher"/>
/// </summary>
/// <param name="dispatcher"></param>
public DispatcherDelegatingHandler(IDispatcher dispatcher)
{
_Dispatcher = dispatcher;
InnerHandler = new HttpClientHandler();
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
return _Dispatcher.Enqueue(() => base.SendAsync(request, cancellationToken), cancellationToken);
}
}
}

View File

@@ -1,73 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ComposableAsync
{
internal class ComposedDispatcher : IDispatcher, IAsyncDisposable
{
private readonly IDispatcher _First;
private readonly IDispatcher _Second;
public ComposedDispatcher(IDispatcher first, IDispatcher second)
{
_First = first;
_Second = second;
}
public void Dispatch(Action action)
{
_First.Dispatch(() => _Second.Dispatch(action));
}
public async Task Enqueue(Action action)
{
await _First.Enqueue(() => _Second.Enqueue(action));
}
public async Task<T> Enqueue<T>(Func<T> action)
{
return await _First.Enqueue(() => _Second.Enqueue(action));
}
public async Task Enqueue(Func<Task> action)
{
await _First.Enqueue(() => _Second.Enqueue(action));
}
public async Task<T> Enqueue<T>(Func<Task<T>> action)
{
return await _First.Enqueue(() => _Second.Enqueue(action));
}
public async Task Enqueue(Func<Task> action, CancellationToken cancellationToken)
{
await _First.Enqueue(() => _Second.Enqueue(action, cancellationToken), cancellationToken);
}
public async Task<T> Enqueue<T>(Func<Task<T>> action, CancellationToken cancellationToken)
{
return await _First.Enqueue(() => _Second.Enqueue(action, cancellationToken), cancellationToken);
}
public async Task<T> Enqueue<T>(Func<T> action, CancellationToken cancellationToken)
{
return await _First.Enqueue(() => _Second.Enqueue(action, cancellationToken), cancellationToken);
}
public async Task Enqueue(Action action, CancellationToken cancellationToken)
{
await _First.Enqueue(() => _Second.Enqueue(action, cancellationToken), cancellationToken);
}
public IDispatcher Clone() => new ComposedDispatcher(_First, _Second);
public Task DisposeAsync()
{
return Task.WhenAll(DisposeAsync(_First), DisposeAsync(_Second));
}
private static Task DisposeAsync(IDispatcher disposable) => (disposable as IAsyncDisposable)?.DisposeAsync() ?? Task.CompletedTask;
}
}

View File

@@ -1,63 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ComposableAsync
{
internal class DispatcherAdapter : IDispatcher
{
private readonly IBasicDispatcher _BasicDispatcher;
public DispatcherAdapter(IBasicDispatcher basicDispatcher)
{
_BasicDispatcher = basicDispatcher;
}
public IDispatcher Clone() => new DispatcherAdapter(_BasicDispatcher.Clone());
public void Dispatch(Action action)
{
_BasicDispatcher.Enqueue(action, CancellationToken.None);
}
public Task Enqueue(Action action)
{
return _BasicDispatcher.Enqueue(action, CancellationToken.None);
}
public Task<T> Enqueue<T>(Func<T> action)
{
return _BasicDispatcher.Enqueue(action, CancellationToken.None);
}
public Task Enqueue(Func<Task> action)
{
return _BasicDispatcher.Enqueue(action, CancellationToken.None);
}
public Task<T> Enqueue<T>(Func<Task<T>> action)
{
return _BasicDispatcher.Enqueue(action, CancellationToken.None);
}
public Task<T> Enqueue<T>(Func<T> action, CancellationToken cancellationToken)
{
return _BasicDispatcher.Enqueue(action, cancellationToken);
}
public Task Enqueue(Action action, CancellationToken cancellationToken)
{
return _BasicDispatcher.Enqueue(action, cancellationToken);
}
public Task Enqueue(Func<Task> action, CancellationToken cancellationToken)
{
return _BasicDispatcher.Enqueue(action, cancellationToken);
}
public Task<T> Enqueue<T>(Func<Task<T>> action, CancellationToken cancellationToken)
{
return _BasicDispatcher.Enqueue(action, cancellationToken);
}
}
}

View File

@@ -1,74 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ComposableAsync
{
/// <summary>
/// <see cref="IDispatcher"/> that run actions synchronously
/// </summary>
public sealed class NullDispatcher: IDispatcher
{
private NullDispatcher() { }
/// <summary>
/// Returns a static null dispatcher
/// </summary>
public static IDispatcher Instance { get; } = new NullDispatcher();
/// <inheritdoc />
public void Dispatch(Action action)
{
action();
}
/// <inheritdoc />
public Task Enqueue(Action action)
{
action();
return Task.CompletedTask;
}
/// <inheritdoc />
public Task<T> Enqueue<T>(Func<T> action)
{
return Task.FromResult(action());
}
/// <inheritdoc />
public async Task Enqueue(Func<Task> action)
{
await action();
}
/// <inheritdoc />
public async Task<T> Enqueue<T>(Func<Task<T>> action)
{
return await action();
}
public Task<T> Enqueue<T>(Func<T> action, CancellationToken cancellationToken)
{
return Task.FromResult(action());
}
public Task Enqueue(Action action, CancellationToken cancellationToken)
{
action();
return Task.CompletedTask;
}
public async Task Enqueue(Func<Task> action, CancellationToken cancellationToken)
{
await action();
}
public async Task<T> Enqueue<T>(Func<Task<T>> action, CancellationToken cancellationToken)
{
return await action();
}
/// <inheritdoc />
public IDispatcher Clone() => Instance;
}
}

View File

@@ -1,90 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
namespace ComposableAsync
{
/// <summary>
/// <see cref="IDispatcher"/> extension methods provider
/// </summary>
public static class DispatcherExtension
{
/// <summary>
/// Returns awaitable to enter in the dispatcher context
/// This extension method make a dispatcher awaitable
/// </summary>
/// <param name="dispatcher"></param>
/// <returns></returns>
public static DispatcherAwaiter GetAwaiter(this IDispatcher dispatcher)
{
return new DispatcherAwaiter(dispatcher);
}
/// <summary>
/// Returns a composed dispatcher applying the given dispatcher
/// after the first one
/// </summary>
/// <param name="dispatcher"></param>
/// <param name="other"></param>
/// <returns></returns>
public static IDispatcher Then(this IDispatcher dispatcher, IDispatcher other)
{
if (dispatcher == null)
throw new ArgumentNullException(nameof(dispatcher));
if (other == null)
throw new ArgumentNullException(nameof(other));
return new ComposedDispatcher(dispatcher, other);
}
/// <summary>
/// Returns a composed dispatcher applying the given dispatchers sequentially
/// </summary>
/// <param name="dispatcher"></param>
/// <param name="others"></param>
/// <returns></returns>
public static IDispatcher Then(this IDispatcher dispatcher, params IDispatcher[] others)
{
return dispatcher.Then((IEnumerable<IDispatcher>)others);
}
/// <summary>
/// Returns a composed dispatcher applying the given dispatchers sequentially
/// </summary>
/// <param name="dispatcher"></param>
/// <param name="others"></param>
/// <returns></returns>
public static IDispatcher Then(this IDispatcher dispatcher, IEnumerable<IDispatcher> others)
{
if (dispatcher == null)
throw new ArgumentNullException(nameof(dispatcher));
if (others == null)
throw new ArgumentNullException(nameof(others));
return others.Aggregate(dispatcher, (cum, val) => cum.Then(val));
}
/// <summary>
/// Create a <see cref="DelegatingHandler"/> from an <see cref="IDispatcher"/>
/// </summary>
/// <param name="dispatcher"></param>
/// <returns></returns>
public static DelegatingHandler AsDelegatingHandler(this IDispatcher dispatcher)
{
return new DispatcherDelegatingHandler(dispatcher);
}
/// <summary>
/// Create a <see cref="IDispatcher"/> from an <see cref="IBasicDispatcher"/>
/// </summary>
/// <param name="basicDispatcher"></param>
/// <returns></returns>
public static IDispatcher ToFullDispatcher(this IBasicDispatcher @basicDispatcher)
{
return new DispatcherAdapter(@basicDispatcher);
}
}
}

View File

@@ -1,19 +0,0 @@
namespace ComposableAsync
{
/// <summary>
/// Dispatcher manager
/// </summary>
public interface IDispatcherManager : IAsyncDisposable
{
/// <summary>
/// true if the Dispatcher should be released
/// </summary>
bool DisposeDispatcher { get; }
/// <summary>
/// Returns a consumable Dispatcher
/// </summary>
/// <returns></returns>
IDispatcher GetDispatcher();
}
}

View File

@@ -1,36 +0,0 @@
using System.Threading.Tasks;
namespace ComposableAsync
{
/// <summary>
/// <see cref="IDispatcherManager"/> implementation based on single <see cref="IDispatcher"/>
/// </summary>
public sealed class MonoDispatcherManager : IDispatcherManager
{
/// <inheritdoc cref="IDispatcherManager"/>
public bool DisposeDispatcher { get; }
/// <inheritdoc cref="IDispatcherManager"/>
public IDispatcher GetDispatcher() => _Dispatcher;
private readonly IDispatcher _Dispatcher;
/// <summary>
/// Create
/// </summary>
/// <param name="dispatcher"></param>
/// <param name="shouldDispose"></param>
public MonoDispatcherManager(IDispatcher dispatcher, bool shouldDispose = false)
{
_Dispatcher = dispatcher;
DisposeDispatcher = shouldDispose;
}
/// <inheritdoc cref="IDispatcherManager"/>
public Task DisposeAsync()
{
return DisposeDispatcher && (_Dispatcher is IAsyncDisposable disposable) ?
disposable.DisposeAsync() : Task.CompletedTask;
}
}
}

View File

@@ -1,18 +0,0 @@
namespace ComposableAsync
{
/// <summary>
/// <see cref="IDispatcherProvider"/> extension
/// </summary>
public static class DispatcherProviderExtension
{
/// <summary>
/// Returns the underlying <see cref="IDispatcher"/>
/// </summary>
/// <param name="dispatcherProvider"></param>
/// <returns></returns>
public static IDispatcher GetAssociatedDispatcher(this IDispatcherProvider dispatcherProvider)
{
return dispatcherProvider?.Dispatcher ?? NullDispatcher.Instance;
}
}
}

View File

@@ -1,48 +0,0 @@
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
namespace ComposableAsync
{
/// <summary>
/// <see cref="IAsyncDisposable"/> implementation aggregating other <see cref="IAsyncDisposable"/>
/// </summary>
public sealed class ComposableAsyncDisposable : IAsyncDisposable
{
private readonly ConcurrentQueue<IAsyncDisposable> _Disposables;
/// <summary>
/// Build an empty ComposableAsyncDisposable
/// </summary>
public ComposableAsyncDisposable()
{
_Disposables = new ConcurrentQueue<IAsyncDisposable>();
}
/// <summary>
/// Add an <see cref="IAsyncDisposable"/> to the ComposableAsyncDisposable
/// and returns it
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="disposable"></param>
/// <returns></returns>
public T Add<T>(T disposable) where T: IAsyncDisposable
{
if (disposable == null)
return default(T);
_Disposables.Enqueue(disposable);
return disposable;
}
/// <summary>
/// Dispose all the resources asynchronously
/// </summary>
/// <returns></returns>
public Task DisposeAsync()
{
var tasks = _Disposables.ToArray().Select(disposable => disposable.DisposeAsync()).ToArray();
return Task.WhenAll(tasks);
}
}
}

View File

@@ -1,17 +0,0 @@
using System.Threading.Tasks;
namespace ComposableAsync
{
/// <summary>
/// Asynchronous version of IDisposable
/// For reference see discussion: https://github.com/dotnet/roslyn/issues/114
/// </summary>
public interface IAsyncDisposable
{
/// <summary>
/// Performs asynchronously application-defined tasks associated with freeing,
/// releasing, or resetting unmanaged resources.
/// </summary>
Task DisposeAsync();
}
}

View File

@@ -1,57 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ComposableAsync
{
/// <summary>
/// Simplified version of <see cref="IDispatcher"/> that can be converted
/// to a <see cref="IDispatcher"/> using the ToFullDispatcher extension method
/// </summary>
public interface IBasicDispatcher
{
/// <summary>
/// Clone dispatcher
/// </summary>
/// <returns></returns>
IBasicDispatcher Clone();
/// <summary>
/// Enqueue the function and return a task corresponding
/// to the execution of the task
/// /// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="action"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<T> Enqueue<T>(Func<T> action, CancellationToken cancellationToken);
/// <summary>
/// Enqueue the action and return a task corresponding
/// to the execution of the task
/// </summary>
/// <param name="action"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task Enqueue(Action action, CancellationToken cancellationToken);
/// <summary>
/// Enqueue the task and return a task corresponding
/// to the execution of the task
/// </summary>
/// <param name="action"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task Enqueue(Func<Task> action, CancellationToken cancellationToken);
/// <summary>
/// Enqueue the task and return a task corresponding
/// to the execution of the original task
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="action"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<T> Enqueue<T>(Func<Task<T>> action, CancellationToken cancellationToken);
}
}

View File

@@ -1,98 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ComposableAsync
{
/// <summary>
/// Dispatcher executes an action or a function
/// on its own context
/// </summary>
public interface IDispatcher
{
/// <summary>
/// Execute action on dispatcher context in a
/// none-blocking way
/// </summary>
/// <param name="action"></param>
void Dispatch(Action action);
/// <summary>
/// Enqueue the action and return a task corresponding to
/// the completion of the action
/// </summary>
/// <param name="action"></param>
/// <returns></returns>
Task Enqueue(Action action);
/// <summary>
/// Enqueue the function and return a task corresponding to
/// the result of the function
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="action"></param>
/// <returns></returns>
Task<T> Enqueue<T>(Func<T> action);
/// <summary>
/// Enqueue the task and return a task corresponding to
/// the completion of the task
/// </summary>
/// <param name="action"></param>
/// <returns></returns>
Task Enqueue(Func<Task> action);
/// <summary>
/// Enqueue the task and return a task corresponding
/// to the execution of the original task
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="action"></param>
/// <returns></returns>
Task<T> Enqueue<T>(Func<Task<T>> action);
/// <summary>
/// Enqueue the function and return a task corresponding
/// to the execution of the task
/// /// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="action"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<T> Enqueue<T>(Func<T> action, CancellationToken cancellationToken);
/// <summary>
/// Enqueue the action and return a task corresponding
/// to the execution of the task
/// </summary>
/// <param name="action"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task Enqueue(Action action, CancellationToken cancellationToken);
/// <summary>
/// Enqueue the task and return a task corresponding
/// to the execution of the task
/// </summary>
/// <param name="action"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task Enqueue(Func<Task> action, CancellationToken cancellationToken);
/// <summary>
/// Enqueue the task and return a task corresponding
/// to the execution of the original task
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="action"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<T> Enqueue<T>(Func<Task<T>> action, CancellationToken cancellationToken);
/// <summary>
/// Clone dispatcher
/// </summary>
/// <returns></returns>
IDispatcher Clone();
}
}

View File

@@ -1,13 +0,0 @@
namespace ComposableAsync
{
/// <summary>
/// Returns the fiber associated with an actor
/// </summary>
public interface IDispatcherProvider
{
/// <summary>
/// Returns the corresponding <see cref="IDispatcher"/>
/// </summary>
IDispatcher Dispatcher { get; }
}
}

View File

@@ -1,22 +0,0 @@
namespace RateLimiter
{
/// <summary>
/// Provides extension to interface <see cref="IAwaitableConstraint"/>
/// </summary>
public static class AwaitableConstraintExtension
{
/// <summary>
/// Compose two awaitable constraint in a new one
/// </summary>
/// <param name="awaitableConstraint1"></param>
/// <param name="awaitableConstraint2"></param>
/// <returns></returns>
public static IAwaitableConstraint Compose(this IAwaitableConstraint awaitableConstraint1, IAwaitableConstraint awaitableConstraint2)
{
if (awaitableConstraint1 == awaitableConstraint2)
return awaitableConstraint1;
return new ComposedAwaitableConstraint(awaitableConstraint1, awaitableConstraint2);
}
}
}

View File

@@ -1,47 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace RateLimiter
{
internal class ComposedAwaitableConstraint : IAwaitableConstraint
{
private readonly IAwaitableConstraint _AwaitableConstraint1;
private readonly IAwaitableConstraint _AwaitableConstraint2;
private readonly SemaphoreSlim _Semaphore = new SemaphoreSlim(1, 1);
internal ComposedAwaitableConstraint(IAwaitableConstraint awaitableConstraint1, IAwaitableConstraint awaitableConstraint2)
{
_AwaitableConstraint1 = awaitableConstraint1;
_AwaitableConstraint2 = awaitableConstraint2;
}
public IAwaitableConstraint Clone()
{
return new ComposedAwaitableConstraint(_AwaitableConstraint1.Clone(), _AwaitableConstraint2.Clone());
}
public async Task<IDisposable> WaitForReadiness(CancellationToken cancellationToken)
{
await _Semaphore.WaitAsync(cancellationToken);
IDisposable[] disposables;
try
{
disposables = await Task.WhenAll(_AwaitableConstraint1.WaitForReadiness(cancellationToken), _AwaitableConstraint2.WaitForReadiness(cancellationToken));
}
catch (Exception)
{
_Semaphore.Release();
throw;
}
return new DisposeAction(() =>
{
foreach (var disposable in disposables)
{
disposable.Dispose();
}
_Semaphore.Release();
});
}
}
}

View File

@@ -1,120 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace RateLimiter
{
/// <summary>
/// Provide an awaitable constraint based on number of times per duration
/// </summary>
public class CountByIntervalAwaitableConstraint : IAwaitableConstraint
{
/// <summary>
/// List of the last time stamps
/// </summary>
public IReadOnlyList<DateTime> TimeStamps => _TimeStamps.ToList();
/// <summary>
/// Stack of the last time stamps
/// </summary>
protected LimitedSizeStack<DateTime> _TimeStamps { get; }
private int _Count { get; }
private TimeSpan _TimeSpan { get; }
private SemaphoreSlim _Semaphore { get; } = new SemaphoreSlim(1, 1);
private ITime _Time { get; }
/// <summary>
/// Constructs a new AwaitableConstraint based on number of times per duration
/// </summary>
/// <param name="count"></param>
/// <param name="timeSpan"></param>
public CountByIntervalAwaitableConstraint(int count, TimeSpan timeSpan) : this(count, timeSpan, TimeSystem.StandardTime)
{
}
internal CountByIntervalAwaitableConstraint(int count, TimeSpan timeSpan, ITime time)
{
if (count <= 0)
throw new ArgumentException("count should be strictly positive", nameof(count));
if (timeSpan.TotalMilliseconds <= 0)
throw new ArgumentException("timeSpan should be strictly positive", nameof(timeSpan));
_Count = count;
_TimeSpan = timeSpan;
_TimeStamps = new LimitedSizeStack<DateTime>(_Count);
_Time = time;
}
/// <summary>
/// returns a task that will complete once the constraint is fulfilled
/// </summary>
/// <param name="cancellationToken">
/// Cancel the wait
/// </param>
/// <returns>
/// A disposable that should be disposed upon task completion
/// </returns>
public async Task<IDisposable> WaitForReadiness(CancellationToken cancellationToken)
{
await _Semaphore.WaitAsync(cancellationToken);
var count = 0;
var now = _Time.GetNow();
var target = now - _TimeSpan;
LinkedListNode<DateTime> element = _TimeStamps.First, last = null;
while ((element != null) && (element.Value > target))
{
last = element;
element = element.Next;
count++;
}
if (count < _Count)
return new DisposeAction(OnEnded);
Debug.Assert(element == null);
Debug.Assert(last != null);
var timeToWait = last.Value.Add(_TimeSpan) - now;
try
{
await _Time.GetDelay(timeToWait, cancellationToken);
}
catch (Exception)
{
_Semaphore.Release();
throw;
}
return new DisposeAction(OnEnded);
}
/// <summary>
/// Clone CountByIntervalAwaitableConstraint
/// </summary>
/// <returns></returns>
public IAwaitableConstraint Clone()
{
return new CountByIntervalAwaitableConstraint(_Count, _TimeSpan, _Time);
}
private void OnEnded()
{
var now = _Time.GetNow();
_TimeStamps.Push(now);
OnEnded(now);
_Semaphore.Release();
}
/// <summary>
/// Called when action has been executed
/// </summary>
/// <param name="now"></param>
protected virtual void OnEnded(DateTime now)
{
}
}
}

View File

@@ -1,20 +0,0 @@
using System;
namespace RateLimiter
{
internal class DisposeAction : IDisposable
{
private Action _Act;
public DisposeAction(Action act)
{
_Act = act;
}
public void Dispose()
{
_Act?.Invoke();
_Act = null;
}
}
}

View File

@@ -1,29 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace RateLimiter
{
/// <summary>
/// Represents a time constraints that can be awaited
/// </summary>
public interface IAwaitableConstraint
{
/// <summary>
/// returns a task that will complete once the constraint is fulfilled
/// </summary>
/// <param name="cancellationToken">
/// Cancel the wait
/// </param>
/// <returns>
/// A disposable that should be disposed upon task completion
/// </returns>
Task<IDisposable> WaitForReadiness(CancellationToken cancellationToken);
/// <summary>
/// Returns a new IAwaitableConstraint with same constraints but unused
/// </summary>
/// <returns></returns>
IAwaitableConstraint Clone();
}
}

View File

@@ -1,26 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace RateLimiter
{
/// <summary>
/// Time abstraction
/// </summary>
internal interface ITime
{
/// <summary>
/// Return Now DateTime
/// </summary>
/// <returns></returns>
DateTime GetNow();
/// <summary>
/// Returns a task delay
/// </summary>
/// <param name="timespan"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task GetDelay(TimeSpan timespan, CancellationToken cancellationToken);
}
}

View File

@@ -1,35 +0,0 @@
using System.Collections.Generic;
namespace RateLimiter
{
/// <summary>
/// LinkedList with a limited size
/// If the size exceeds the limit older entry are removed
/// </summary>
/// <typeparam name="T"></typeparam>
public class LimitedSizeStack<T>: LinkedList<T>
{
private readonly int _MaxSize;
/// <summary>
/// Construct the LimitedSizeStack with the given limit
/// </summary>
/// <param name="maxSize"></param>
public LimitedSizeStack(int maxSize)
{
_MaxSize = maxSize;
}
/// <summary>
/// Push new entry. If he size exceeds the limit, the oldest entry is removed
/// </summary>
/// <param name="item"></param>
public void Push(T item)
{
AddFirst(item);
if (Count > _MaxSize)
RemoveLast();
}
}
}

View File

@@ -1,42 +0,0 @@
using System;
using System.Collections.Generic;
namespace RateLimiter
{
/// <summary>
/// <see cref="CountByIntervalAwaitableConstraint"/> that is able to save own state.
/// </summary>
public sealed class PersistentCountByIntervalAwaitableConstraint : CountByIntervalAwaitableConstraint
{
private readonly Action<DateTime> _SaveStateAction;
/// <summary>
/// Create an instance of <see cref="PersistentCountByIntervalAwaitableConstraint"/>.
/// </summary>
/// <param name="count">Maximum actions allowed per time interval.</param>
/// <param name="timeSpan">Time interval limits are applied for.</param>
/// <param name="saveStateAction">Action is used to save state.</param>
/// <param name="initialTimeStamps">Initial timestamps.</param>
public PersistentCountByIntervalAwaitableConstraint(int count, TimeSpan timeSpan,
Action<DateTime> saveStateAction, IEnumerable<DateTime> initialTimeStamps) : base(count, timeSpan)
{
_SaveStateAction = saveStateAction;
if (initialTimeStamps == null)
return;
foreach (var timeStamp in initialTimeStamps)
{
_TimeStamps.Push(timeStamp);
}
}
/// <summary>
/// Save state
/// </summary>
protected override void OnEnded(DateTime now)
{
_SaveStateAction(now);
}
}
}

View File

@@ -1,206 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ComposableAsync;
namespace RateLimiter
{
/// <summary>
/// TimeLimiter implementation
/// </summary>
public class TimeLimiter : IDispatcher
{
private readonly IAwaitableConstraint _AwaitableConstraint;
internal TimeLimiter(IAwaitableConstraint awaitableConstraint)
{
_AwaitableConstraint = awaitableConstraint;
}
/// <summary>
/// Perform the given task respecting the time constraint
/// returning the result of given function
/// </summary>
/// <param name="perform"></param>
/// <returns></returns>
public Task Enqueue(Func<Task> perform)
{
return Enqueue(perform, CancellationToken.None);
}
/// <summary>
/// Perform the given task respecting the time constraint
/// returning the result of given function
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="perform"></param>
/// <returns></returns>
public Task<T> Enqueue<T>(Func<Task<T>> perform)
{
return Enqueue(perform, CancellationToken.None);
}
/// <summary>
/// Perform the given task respecting the time constraint
/// </summary>
/// <param name="perform"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task Enqueue(Func<Task> perform, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
using (await _AwaitableConstraint.WaitForReadiness(cancellationToken))
{
await perform();
}
}
/// <summary>
/// Perform the given task respecting the time constraint
/// returning the result of given function
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="perform"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<T> Enqueue<T>(Func<Task<T>> perform, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
using (await _AwaitableConstraint.WaitForReadiness(cancellationToken))
{
return await perform();
}
}
public IDispatcher Clone() => new TimeLimiter(_AwaitableConstraint.Clone());
private static Func<Task> Transform(Action act)
{
return () => { act(); return Task.FromResult(0); };
}
/// <summary>
/// Perform the given task respecting the time constraint
/// returning the result of given function
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="compute"></param>
/// <returns></returns>
private static Func<Task<T>> Transform<T>(Func<T> compute)
{
return () => Task.FromResult(compute());
}
/// <summary>
/// Perform the given task respecting the time constraint
/// </summary>
/// <param name="perform"></param>
/// <returns></returns>
public Task Enqueue(Action perform)
{
var transformed = Transform(perform);
return Enqueue(transformed);
}
/// <summary>
/// Perform the given task respecting the time constraint
/// </summary>
/// <param name="action"></param>
public void Dispatch(Action action)
{
Enqueue(action);
}
/// <summary>
/// Perform the given task respecting the time constraint
/// returning the result of given function
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="perform"></param>
/// <returns></returns>
public Task<T> Enqueue<T>(Func<T> perform)
{
var transformed = Transform(perform);
return Enqueue(transformed);
}
/// <summary>
/// Perform the given task respecting the time constraint
/// returning the result of given function
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="perform"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public Task<T> Enqueue<T>(Func<T> perform, CancellationToken cancellationToken)
{
var transformed = Transform(perform);
return Enqueue(transformed, cancellationToken);
}
/// <summary>
/// Perform the given task respecting the time constraint
/// </summary>
/// <param name="perform"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public Task Enqueue(Action perform, CancellationToken cancellationToken)
{
var transformed = Transform(perform);
return Enqueue(transformed, cancellationToken);
}
/// <summary>
/// Returns a TimeLimiter based on a maximum number of times
/// during a given period
/// </summary>
/// <param name="maxCount"></param>
/// <param name="timeSpan"></param>
/// <returns></returns>
public static TimeLimiter GetFromMaxCountByInterval(int maxCount, TimeSpan timeSpan)
{
return new TimeLimiter(new CountByIntervalAwaitableConstraint(maxCount, timeSpan));
}
/// <summary>
/// Create <see cref="TimeLimiter"/> that will save state using action passed through <paramref name="saveStateAction"/> parameter.
/// </summary>
/// <param name="maxCount">Maximum actions allowed per time interval.</param>
/// <param name="timeSpan">Time interval limits are applied for.</param>
/// <param name="saveStateAction">Action is used to save state.</param>
/// <returns><see cref="TimeLimiter"/> instance with <see cref="PersistentCountByIntervalAwaitableConstraint"/>.</returns>
public static TimeLimiter GetPersistentTimeLimiter(int maxCount, TimeSpan timeSpan,
Action<DateTime> saveStateAction)
{
return GetPersistentTimeLimiter(maxCount, timeSpan, saveStateAction, null);
}
/// <summary>
/// Create <see cref="TimeLimiter"/> with initial timestamps that will save state using action passed through <paramref name="saveStateAction"/> parameter.
/// </summary>
/// <param name="maxCount">Maximum actions allowed per time interval.</param>
/// <param name="timeSpan">Time interval limits are applied for.</param>
/// <param name="saveStateAction">Action is used to save state.</param>
/// <param name="initialTimeStamps">Initial timestamps.</param>
/// <returns><see cref="TimeLimiter"/> instance with <see cref="PersistentCountByIntervalAwaitableConstraint"/>.</returns>
public static TimeLimiter GetPersistentTimeLimiter(int maxCount, TimeSpan timeSpan,
Action<DateTime> saveStateAction, IEnumerable<DateTime> initialTimeStamps)
{
return new TimeLimiter(new PersistentCountByIntervalAwaitableConstraint(maxCount, timeSpan, saveStateAction, initialTimeStamps));
}
/// <summary>
/// Compose various IAwaitableConstraint in a TimeLimiter
/// </summary>
/// <param name="constraints"></param>
/// <returns></returns>
public static TimeLimiter Compose(params IAwaitableConstraint[] constraints)
{
var composed = constraints.Aggregate(default(IAwaitableConstraint),
(accumulated, current) => (accumulated == null) ? current : accumulated.Compose(current));
return new TimeLimiter(composed);
}
}
}

View File

@@ -1,30 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace RateLimiter
{
internal class TimeSystem : ITime
{
public static ITime StandardTime { get; }
static TimeSystem()
{
StandardTime = new TimeSystem();
}
private TimeSystem()
{
}
DateTime ITime.GetNow()
{
return DateTime.Now;
}
Task ITime.GetDelay(TimeSpan timespan, CancellationToken cancellationToken)
{
return Task.Delay(timespan, cancellationToken);
}
}
}

View File

@@ -74,11 +74,7 @@ ass格式
```sh
dotnet restore
dotnet publish --output=artifacts Jellyfin.Plugin.Danmu/Jellyfin.Plugin.Danmu.csproj
# remove unused dll
cd artifacts
rm -rf MediaBrowser*.dll Microsoft*.dll Newtonsoft*.dll System*.dll Emby*.dll Jellyfin.Data*.dll Jellyfin.Extensions*.dll *.json *.pdb
dotnet publish Jellyfin.Plugin.Danmu/Jellyfin.Plugin.Danmu.csproj
```
@@ -86,7 +82,7 @@ rm -rf MediaBrowser*.dll Microsoft*.dll Newtonsoft*.dll System*.dll Emby*.dll Je
1. Build the plugin
2. Create a folder, like `danmu` and copy `artifacts/*.dll` into it
2. Create a folder, like `danmu` and copy `./Jellyfin.Plugin.Danmu/bin/Debug/net6.0/Jellyfin.Plugin.Danmu.dll` into it
3. Move folder `danmu` to jellyfin `data/plugins` folder