RegexBot/Module/VoteTempChannel/VoteStore.cs

148 lines
4.7 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
using System.Linq;
namespace Noikoio.RegexBot.Module.VoteTempChannel
{
/// <summary>
/// Handles keeping track of per-guild voting, along with cooldowns.
/// </summary>
class VoteStore
{
/*
* Votes are kept track for a total of five minutes beginning with the first vote.
* All votes are discarded after five minutes have elapsed, rather than the data
* staying for longer as more votes are added.
*/
private Dictionary<ulong, DateTimeOffset> _cooldown;
private Dictionary<ulong, VoteData> _votes;
private class VoteData
{
public VoteData()
{
VotingStart = DateTimeOffset.UtcNow;
Voters = new List<ulong>();
}
public DateTimeOffset VotingStart { get; }
public List<ulong> Voters { get; }
}
public VoteStore()
{
_cooldown = new Dictionary<ulong, DateTimeOffset>();
_votes = new Dictionary<ulong, VoteData>();
}
// !! Hardcoded value: votes always expire after 5 minutes.
static readonly TimeSpan VoteExpiry = new TimeSpan(0, 5, 0);
/// <summary>
/// Call before accessing votes. Removes any stale voting entries.
/// </summary>
private void CleanVoteData()
{
IEnumerable<Tuple<ulong, DateTimeOffset>> expiredEntries;
lock (_votes)
{
var now = DateTimeOffset.UtcNow;
expiredEntries = (from item in _votes
where now > item.Value.VotingStart + VoteExpiry
select new Tuple<ulong, DateTimeOffset>(item.Key, item.Value.VotingStart))
.ToArray();
lock (_cooldown)
{
// For expiring votes, set a cooldown that starts at the time the
// vote had actually expired.
foreach (var item in expiredEntries)
{
_votes.Remove(item.Item1);
_cooldown.Add(item.Item1, item.Item2 + VoteExpiry);
}
}
}
}
// !! Hardcoded value: cooldowns last one hour.
static readonly TimeSpan CooldownExpiry = new TimeSpan(1, 0, 0);
private bool IsInCooldown(ulong guild)
{
lock (_cooldown)
{
// Clean up expired entries first...
var now = DateTimeOffset.UtcNow;
var expiredEntries = (from item in _cooldown
where now > item.Value + CooldownExpiry
select item.Key).ToArray();
foreach (var item in expiredEntries) _cooldown.Remove(item);
// And then the actual check:
return _cooldown.ContainsKey(guild);
}
}
public void SetCooldown(ulong guild)
{
lock (_cooldown) _cooldown.Add(guild, DateTimeOffset.UtcNow);
}
public void ClearCooldown(ulong guild)
{
lock (_cooldown) _cooldown.Remove(guild);
}
/// <summary>
/// Attempts to log a vote by a given user.
/// </summary>
public VoteStatus AddVote(ulong guild, ulong user, out int voteCount)
{
voteCount = -1;
if (IsInCooldown(guild)) return VoteStatus.FailCooldown;
lock (_votes)
{
CleanVoteData();
VoteData v;
if (!_votes.TryGetValue(guild, out v))
{
v = new VoteData();
_votes[guild] = v;
}
voteCount = v.Voters.Count;
if (v.Voters.Contains(user)) return VoteStatus.FailVotedAlready;
v.Voters.Add(user);
voteCount++;
return VoteStatus.Success;
}
}
public void DelVote(ulong guild, ulong user)
{
lock (_votes)
{
if (_votes.TryGetValue(guild, out var v))
{
v.Voters.Remove(user);
if (v.Voters.Count == 0) _votes.Remove(guild);
}
}
}
/// <summary>
/// Clears voting data from within the specified guild.
/// </summary>
public void ClearVotes(ulong guild)
{
lock (_votes) _votes.Remove(guild);
}
}
enum VoteStatus
{
Success, FailVotedAlready, FailCooldown
}
}