Replaced VoteStore with VotingSession

This commit is contained in:
Noikoio 2018-10-28 18:39:48 -07:00
parent b9b23e5b02
commit 0a2acf6ed6
3 changed files with 79 additions and 151 deletions

View file

@ -5,16 +5,17 @@ using System.Text.RegularExpressions;
namespace Noikoio.RegexBot.Module.VoteTempChannel namespace Noikoio.RegexBot.Module.VoteTempChannel
{ {
class GuildConfiguration class Configuration
{ {
public string VoteCommand { get; } public string VoteCommand { get; }
public string TempChannelName { get; } public string TempChannelName { get; }
public TimeSpan ChannelBaseDuration { get; } public TimeSpan ChannelBaseDuration { get; }
public TimeSpan ChannelExtendDuration { get; } public TimeSpan ChannelExtendDuration { get; }
public TimeSpan KeepaliveVoteDuration { get; }
public int VotePassThreshold { get; } public int VotePassThreshold { get; }
public TimeSpan VotingDuration { get; }
public TimeSpan VotingCooldown { get; }
public GuildConfiguration(JObject j) public Configuration(JObject j)
{ {
VoteCommand = j["VoteCommand"]?.Value<string>(); VoteCommand = j["VoteCommand"]?.Value<string>();
if (string.IsNullOrWhiteSpace(VoteCommand)) if (string.IsNullOrWhiteSpace(VoteCommand))
@ -39,7 +40,8 @@ namespace Noikoio.RegexBot.Module.VoteTempChannel
ChannelBaseDuration = ParseTimeConfig(j, "ChannelBaseDuration"); ChannelBaseDuration = ParseTimeConfig(j, "ChannelBaseDuration");
ChannelExtendDuration = ParseTimeConfig(j, "ChannelExtendDuration"); ChannelExtendDuration = ParseTimeConfig(j, "ChannelExtendDuration");
KeepaliveVoteDuration = ParseTimeConfig(j, "KeepaliveVoteDuration"); VotingDuration = ParseTimeConfig(j, "VotingDuration");
VotingCooldown = ParseTimeConfig(j, "VotingCooldown");
} }
private TimeSpan ParseTimeConfig(JObject conf, string valueName) private TimeSpan ParseTimeConfig(JObject conf, string valueName)

View file

@ -1,147 +0,0 @@
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
}
}

View file

@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
namespace Noikoio.RegexBot.Module.VoteTempChannel
{
/// <summary>
/// Keeps information on voting sessions and voting cooldowns.
/// </summary>
class VotingSession
{
private Configuration _conf;
private DateTimeOffset _initialVoteTime;
private List<ulong> _votes;
public DateTimeOffset? CooldownStart { get; private set; }
public VotingSession()
{
CooldownStart = null;
_votes = new List<ulong>();
}
/// <summary>
/// Counts a user vote.
/// </summary>
/// <returns>False if the user already has a vote counted.</returns>
public bool AddVote(ulong id)
{
// Mark the start of a new session, if applicable.
if (_votes.Count == 0) _initialVoteTime = DateTimeOffset.UtcNow;
if (_votes.Contains(id)) return false;
_votes.Add(id);
return true;
}
/// <summary>
/// Checks if the voting session has expired.
/// To be called by the background task. This automatically resets state.
/// </summary>
public bool IsSessionExpired()
{
if (_votes.Count == 0) return false;
if (DateTimeOffset.UtcNow > _initialVoteTime + _conf.VotingDuration)
{
// Clear votes. And because we're clearing it due to an expiration, set a cooldown.
Reset();
StartCooldown();
return true;
}
return false;
}
public void StartCooldown()
{
CooldownStart = DateTimeOffset.UtcNow;
}
public bool IsInCooldown()
{
if (!CooldownStart.HasValue) return false;
return CooldownStart.Value + _conf.VotingCooldown > DateTimeOffset.UtcNow;
}
/// <summary>
/// Resets the object to its initial state.
/// </summary>
public void Reset()
{
_votes.Clear();
CooldownStart = null;
}
}
}