39132be4e8
PR Checks / test-and-build (pull_request) Successful in 6m6s
Move neutral join/leave handlers into GmRelay.Shared so Telegram and Discord share capacity, waitlist, duplicate-click, and schedule-update behavior. Add Discord component routing for join_session and leave_session buttons with deferred ephemeral replies and serialized schedule message updates. Bump version to 2.5.0 and update Discord docs. Refs #29
40 lines
1.3 KiB
C#
40 lines
1.3 KiB
C#
using System.Collections.Concurrent;
|
|
using GmRelay.Shared.Platform;
|
|
|
|
namespace GmRelay.Shared.Features.Sessions.CreateSession;
|
|
|
|
public interface IScheduleMessageUpdateLock
|
|
{
|
|
ValueTask<IAsyncDisposable> AcquireAsync(PlatformMessageRef scheduleMessage, CancellationToken ct);
|
|
}
|
|
|
|
public sealed class ScheduleMessageUpdateLock : IScheduleMessageUpdateLock
|
|
{
|
|
private readonly ConcurrentDictionary<string, SemaphoreSlim> locks = new(StringComparer.Ordinal);
|
|
|
|
public async ValueTask<IAsyncDisposable> AcquireAsync(PlatformMessageRef scheduleMessage, CancellationToken ct)
|
|
{
|
|
var key = CreateKey(scheduleMessage);
|
|
var semaphore = locks.GetOrAdd(key, _ => new SemaphoreSlim(1, 1));
|
|
await semaphore.WaitAsync(ct);
|
|
return new Releaser(semaphore);
|
|
}
|
|
|
|
private static string CreateKey(PlatformMessageRef scheduleMessage) =>
|
|
string.Join(
|
|
'\u001F',
|
|
scheduleMessage.Platform.ToString(),
|
|
scheduleMessage.ExternalGroupId,
|
|
scheduleMessage.ExternalThreadId ?? string.Empty,
|
|
scheduleMessage.ExternalMessageId);
|
|
|
|
private sealed class Releaser(SemaphoreSlim semaphore) : IAsyncDisposable
|
|
{
|
|
public ValueTask DisposeAsync()
|
|
{
|
|
semaphore.Release();
|
|
return ValueTask.CompletedTask;
|
|
}
|
|
}
|
|
}
|