Files
SocialPub/PrivaPub/Federation/Moderation/DomainBlocks.cs
T

79 lines
1.9 KiB
C#
Raw Normal View History

using MongoDB.Entities;
using PrivaPub.Models.Federation;
namespace PrivaPub.Federation.Moderation
{
public interface IDomainBlocks
{
DomainBlock Find(string host);
bool IsSuspended(string host);
Task Reload(CancellationToken token);
}
public class DomainBlocks : IDomainBlocks
{
static readonly TimeSpan Staleness = TimeSpan.FromMinutes(5);
readonly ILogger<DomainBlocks> _logger;
IReadOnlyDictionary<string, DomainBlock> _blocks = new Dictionary<string, DomainBlock>();
DateTime _loadedAt = DateTime.MinValue;
int _reloading;
public DomainBlocks(ILogger<DomainBlocks> logger)
{
_logger = logger;
}
public static string Normalise(string domain) => domain?.Trim().Trim('.').ToLowerInvariant();
public DomainBlock Find(string host)
{
RefreshIfStale();
host = Normalise(host);
var blocks = _blocks;
while (!string.IsNullOrEmpty(host))
{
if (blocks.TryGetValue(host, out var block))
return block;
var dot = host.IndexOf('.');
host = dot < 0 ? default : host[(dot + 1)..];
}
return default;
}
public bool IsSuspended(string host) => Find(host)?.Severity == DomainBlockSeverity.Suspend;
2026-10-03 10:36:15 +02:00
public async Task Reload(CancellationToken token) =>
Load(await DB.Default.Find<DomainBlock>().ExecuteAsync(token));
public void Load(IEnumerable<DomainBlock> blocks)
{
_blocks = blocks.Where(b => !string.IsNullOrEmpty(b.Domain))
.GroupBy(b => Normalise(b.Domain))
.ToDictionary(g => g.Key, g => g.First());
_loadedAt = DateTime.UtcNow;
}
void RefreshIfStale()
{
if (DateTime.UtcNow - _loadedAt < Staleness || Interlocked.Exchange(ref _reloading, 1) == 1)
return;
_ = Task.Run(async () =>
{
try
{
await Reload(CancellationToken.None);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Domain blocks could not be reloaded");
}
finally
{
Interlocked.Exchange(ref _reloading, 0);
}
});
}
}
}