// SPDX-License-Identifier: AGPL-3.0-or-later // Copyright (C) 2022-2026 Aykut Alparslan KOC using Ferrite.Data.Counters; using Ferrite.Data.MessageBoxes; using Ferrite.Data.Primitives; namespace Ferrite.Data.UpdateState; public class FasterUpdatesContext : IUpdatesContext { private static readonly System.Collections.Concurrent.ConcurrentDictionary PendingPublications = new(); private static readonly System.Collections.Concurrent.ConcurrentDictionary Delivered = new(); private static readonly TimeSpan PublicationWait = TimeSpan.FromSeconds(2); private readonly long? _authKeyId; private readonly long _userId; private readonly IAtomicCounter _counter; private readonly IMessageBox _commonMessageBox; private readonly ISecretMessageBox? _secondaryMessageBox; private readonly FasterSettledPts _settledPts; public FasterUpdatesContext(FasterContext counterContext, FasterContext> unreadContext, FasterContext> dialogContext, FasterContext settledPtsContext, long? authKeyId, long userId) { _authKeyId = authKeyId; _userId = userId; _settledPts = new FasterSettledPts(settledPtsContext, userId); _counter = new FasterCounter(counterContext, authKeyId != null ? $"seq:updates:auth:{authKeyId}" : $"seq:updates:{userId}"); _commonMessageBox = new FasterMessageBox(counterContext, unreadContext, dialogContext, userId); _secondaryMessageBox = authKeyId != null ? new FasterSecretMessageBox(counterContext, (long)authKeyId) : null; } public async ValueTask Pts() { return await _commonMessageBox.Pts(); } public async ValueTask IncrementPtsForMessage(int peerType, long peerId, int messageId) { return await _commonMessageBox.IncrementPtsForMessage(peerType, peerId, messageId); } public async ValueTask NextMessageId() { return await _commonMessageBox.NextMessageId(); } public async ValueTask ReadMessages(int peerType, long peerId, int maxId) { return await _commonMessageBox.ReadMessages(peerType, peerId, maxId); } public async ValueTask ReadMessagesMaxId(int peerType, long peerId) { return await _commonMessageBox.ReadMessagesMaxId(peerType, peerId); } public async ValueTask UnreadMessages() { return await _commonMessageBox.UnreadMessages(); } public async ValueTask UnreadMessages(int peerType, long peerId) { return await _commonMessageBox.UnreadMessages(peerType, peerId); } public async ValueTask IncrementPts() { int pts = await _commonMessageBox.IncrementPts(); await RecordSessionDelivery(pts); return pts; } public async ValueTask IncrementPts(int count) { int pts = await _commonMessageBox.IncrementPts(count); await RecordSessionDelivery(pts); return pts; } private ValueTask RecordSessionDelivery(int pts) => _authKeyId != null ? AdvanceDeliveredPts(pts) : ValueTask.CompletedTask; public async ValueTask Qts() { return _secondaryMessageBox != null ? await _secondaryMessageBox.Qts() : 0; } public async ValueTask IncrementQts() { return _secondaryMessageBox != null ? await _secondaryMessageBox.IncrementQts() : 0; } public async Task Seq() { return (int)await _counter.Get(); } public async Task IncrementSeq() { return (int)await _counter.IncrementAndGet(); } public ValueTask BeginPtsPublication() { PendingPublications.AddOrUpdate(_userId, 1, static (_, count) => count + 1); return ValueTask.CompletedTask; } public ValueTask CompletePtsPublication() { while (PendingPublications.TryGetValue(_userId, out int count)) { if (count <= 1) { if (PendingPublications.TryRemove( new KeyValuePair(_userId, count))) { break; } } else if (PendingPublications.TryUpdate(_userId, count - 1, count)) { break; } } return ValueTask.CompletedTask; } public async ValueTask WaitForPtsPublications() { DateTime deadline = DateTime.UtcNow + PublicationWait; while (PendingPublications.TryGetValue(_userId, out int count) && count > 0) { if (DateTime.UtcNow >= deadline) return; await Task.Delay(1); } } public ValueTask DeliveredPts() => ValueTask.FromResult(Delivered.TryGetValue(_userId, out int pts) ? pts : 0); public ValueTask AdvanceDeliveredPts(int pts) { Delivered.AddOrUpdate(_userId, pts, (_, existing) => Math.Max(existing, pts)); return ValueTask.CompletedTask; } public ValueTask PendingPtsPublications() { return ValueTask.FromResult(PendingPublications.TryGetValue(_userId, out int count) ? count : 0); } public ValueTask SettlePts(int first, int last) => _settledPts.Settle(first, last); public ValueTask ExtendCommittedPts(int committed) => _settledPts.Extend(committed); }