Merge pull request #880 from 13xforever/vnext

Update handling of bot usage stats
This commit is contained in:
clienthax 2022-07-04 18:37:27 +01:00 committed by GitHub
commit 6bfc26743e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 586 additions and 59 deletions

View File

@ -63,8 +63,7 @@ internal class BaseCommandModuleCustom : BaseCommandModule
{
if (ctx.Command?.QualifiedName is string qualifiedName)
{
StatsStorage.CmdStatCache.TryGetValue(qualifiedName, out int counter);
StatsStorage.CmdStatCache.Set(qualifiedName, ++counter, StatsStorage.CacheTime);
StatsStorage.IncCmdStat(qualifiedName);
Config.TelemetryClient?.TrackRequest(qualifiedName, executionStart, DateTimeOffset.UtcNow - executionStart, HttpStatusCode.OK.ToString(), true);
}

View File

@ -171,12 +171,7 @@ internal sealed class BotStats: BaseCommandModuleCustom
private static void AppendCmdStats(DiscordEmbedBuilder embed)
{
var commandStats = StatsStorage.CmdStatCache.GetCacheKeys<string>();
var sortedCommandStats = commandStats
.Select(c => (name: c, stat: StatsStorage.CmdStatCache.Get(c) as int?))
.Where(c => c.stat.HasValue)
.OrderByDescending(c => c.stat)
.ToList();
var sortedCommandStats = StatsStorage.GetCmdStats();
var totalCalls = sortedCommandStats.Sum(c => c.stat);
var top = sortedCommandStats.Take(5).ToList();
if (top.Count == 0)
@ -192,12 +187,7 @@ internal sealed class BotStats: BaseCommandModuleCustom
private static void AppendExplainStats(DiscordEmbedBuilder embed)
{
var terms = StatsStorage.ExplainStatCache.GetCacheKeys<string>();
var sortedTerms = terms
.Select(t => (term: t, stat: StatsStorage.ExplainStatCache.Get(t) as int?))
.Where(t => t.stat.HasValue)
.OrderByDescending(t => t.stat)
.ToList();
var sortedTerms = StatsStorage.GetExplainStats();
var totalExplains = sortedTerms.Sum(t => t.stat);
var top = sortedTerms.Take(5).ToList();
if (top.Count == 0)
@ -213,12 +203,7 @@ internal sealed class BotStats: BaseCommandModuleCustom
private static void AppendGameLookupStats(DiscordEmbedBuilder embed)
{
var gameTitles = StatsStorage.GameStatCache.GetCacheKeys<string>();
var sortedTitles = gameTitles
.Select(t => (title: t, stat: StatsStorage.GameStatCache.Get(t) as int?))
.Where(t => t.stat.HasValue)
.OrderByDescending(t => t.stat)
.ToList();
var sortedTitles = StatsStorage.GetGameStats();
var totalLookups = sortedTitles.Sum(t => t.stat);
var top = sortedTitles.Take(5).ToList();
if (top.Count == 0)

View File

@ -508,10 +508,7 @@ internal sealed class CompatList : BaseCommandModuleCustom
|| (t.info.Title?.StartsWith(searchTerm, StringComparison.InvariantCultureIgnoreCase) ?? false)
|| (t.info.AlternativeTitle?.StartsWith(searchTerm, StringComparison.InvariantCultureIgnoreCase) ?? false));
foreach (var title in searchHits.Select(t => t.info.Title).Distinct())
{
StatsStorage.GameStatCache.TryGetValue(title, out int stat);
StatsStorage.GameStatCache.Set(title, ++stat, StatsStorage.CacheTime);
}
StatsStorage.IncGameStat(title);
foreach (var resultInfo in sortedList.Take(request.AmountRequested))
{
var info = resultInfo.AsString();

View File

@ -397,8 +397,7 @@ internal sealed class Explain: BaseCommandModuleCustom
}
var explain = termLookupResult.explanation;
StatsStorage.ExplainStatCache.TryGetValue(explain.Keyword, out int stat);
StatsStorage.ExplainStatCache.Set(explain.Keyword, ++stat, StatsStorage.CacheTime);
StatsStorage.IncExplainStat(explain.Keyword);
msgBuilder = new DiscordMessageBuilder().WithContent(explain.Text);
if (!usedReply && useReply)
msgBuilder.WithReply(sourceMessage.Id);

View File

@ -39,10 +39,10 @@
<AdditionalFiles Include="..\win32_error_codes.txt" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="DSharpPlus" Version="4.3.0-nightly-01149" />
<PackageReference Include="DSharpPlus.CommandsNext" Version="4.3.0-nightly-01149" />
<PackageReference Include="DSharpPlus.Interactivity" Version="4.3.0-nightly-01149" />
<PackageReference Include="DSharpPlus.SlashCommands" Version="4.3.0-nightly-01149" />
<PackageReference Include="DSharpPlus" Version="4.3.0-nightly-01146" />
<PackageReference Include="DSharpPlus.CommandsNext" Version="4.3.0-nightly-01146" />
<PackageReference Include="DSharpPlus.Interactivity" Version="4.3.0-nightly-01146" />
<PackageReference Include="DSharpPlus.SlashCommands" Version="4.3.0-nightly-01146" />
<PackageReference Include="Google.Apis.Drive.v3" Version="1.57.0.2684" />
<PackageReference Include="ksemenenko.ColorThief" Version="1.1.1.4" />
<PackageReference Include="MathParser.org-mXparser" Version="5.0.6" />

View File

@ -44,7 +44,7 @@ internal class BotDb: DbContext
modelBuilder.Entity<DisabledCommand>().HasIndex(c => c.Command).IsUnique().HasDatabaseName("disabled_command_command");
modelBuilder.Entity<WhitelistedInvite>().HasIndex(i => i.GuildId).IsUnique().HasDatabaseName("whitelisted_invite_guild_id");
modelBuilder.Entity<EventSchedule>().HasIndex(e => new {e.Year, e.EventName}).HasDatabaseName("event_schedule_year_event_name");
modelBuilder.Entity<Stats>().HasIndex(s => new { s.Category, s.Key }).IsUnique().HasDatabaseName("stats_category_key");
modelBuilder.Entity<Stats>().HasIndex(s => new { s.Category, s.Bucket, s.Key }).IsUnique().HasDatabaseName("stats_category_bucket_key");
modelBuilder.Entity<Kot>().HasIndex(k => k.UserId).IsUnique().HasDatabaseName("kot_user_id");
modelBuilder.Entity<Doggo>().HasIndex(d => d.UserId).IsUnique().HasDatabaseName("doggo_user_id");
modelBuilder.Entity<ForcedNickname>().HasIndex(d => new { d.GuildId, d.UserId }).IsUnique().HasDatabaseName("forced_nickname_guild_id_user_id");
@ -169,6 +169,7 @@ internal class Stats
public int Id { get; set; }
[Required]
public string Category { get; set; } = null!;
public string? Bucket { get; set; }
[Required]
public string Key { get; set; } = null!;
public int Value { get; set; }

View File

@ -0,0 +1,433 @@
// <auto-generated />
using System;
using CompatBot.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace CompatBot.Database.Migrations
{
[DbContext(typeof(BotDb))]
[Migration("20220704163631_AddStatsBucketColumn")]
partial class AddStatsBucketColumn
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "6.0.6");
modelBuilder.Entity("CompatBot.Database.BotState", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("id");
b.Property<string>("Key")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("key");
b.Property<string>("Value")
.HasColumnType("TEXT")
.HasColumnName("value");
b.HasKey("Id")
.HasName("id");
b.HasIndex("Key")
.IsUnique()
.HasDatabaseName("bot_state_key");
b.ToTable("bot_state");
});
modelBuilder.Entity("CompatBot.Database.DisabledCommand", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("id");
b.Property<string>("Command")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("command");
b.HasKey("Id")
.HasName("id");
b.HasIndex("Command")
.IsUnique()
.HasDatabaseName("disabled_command_command");
b.ToTable("disabled_commands");
});
modelBuilder.Entity("CompatBot.Database.Doggo", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("id");
b.Property<ulong>("UserId")
.HasColumnType("INTEGER")
.HasColumnName("user_id");
b.HasKey("Id")
.HasName("id");
b.HasIndex("UserId")
.IsUnique()
.HasDatabaseName("doggo_user_id");
b.ToTable("doggo");
});
modelBuilder.Entity("CompatBot.Database.EventSchedule", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("id");
b.Property<long>("End")
.HasColumnType("INTEGER")
.HasColumnName("end");
b.Property<string>("EventName")
.HasColumnType("TEXT")
.HasColumnName("event_name");
b.Property<string>("Name")
.HasColumnType("TEXT")
.HasColumnName("name");
b.Property<long>("Start")
.HasColumnType("INTEGER")
.HasColumnName("start");
b.Property<int>("Year")
.HasColumnType("INTEGER")
.HasColumnName("year");
b.HasKey("Id")
.HasName("id");
b.HasIndex("Year", "EventName")
.HasDatabaseName("event_schedule_year_event_name");
b.ToTable("event_schedule");
});
modelBuilder.Entity("CompatBot.Database.Explanation", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("id");
b.Property<byte[]>("Attachment")
.HasMaxLength(7340032)
.HasColumnType("BLOB")
.HasColumnName("attachment");
b.Property<string>("AttachmentFilename")
.HasColumnType("TEXT")
.HasColumnName("attachment_filename");
b.Property<string>("Keyword")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("keyword");
b.Property<string>("Text")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("text");
b.HasKey("Id")
.HasName("id");
b.HasIndex("Keyword")
.IsUnique()
.HasDatabaseName("explanation_keyword");
b.ToTable("explanation");
});
modelBuilder.Entity("CompatBot.Database.ForcedNickname", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("id");
b.Property<ulong>("GuildId")
.HasColumnType("INTEGER")
.HasColumnName("guild_id");
b.Property<string>("Nickname")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("nickname");
b.Property<ulong>("UserId")
.HasColumnType("INTEGER")
.HasColumnName("user_id");
b.HasKey("Id")
.HasName("id");
b.HasIndex("GuildId", "UserId")
.IsUnique()
.HasDatabaseName("forced_nickname_guild_id_user_id");
b.ToTable("forced_nicknames");
});
modelBuilder.Entity("CompatBot.Database.Kot", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("id");
b.Property<ulong>("UserId")
.HasColumnType("INTEGER")
.HasColumnName("user_id");
b.HasKey("Id")
.HasName("id");
b.HasIndex("UserId")
.IsUnique()
.HasDatabaseName("kot_user_id");
b.ToTable("kot");
});
modelBuilder.Entity("CompatBot.Database.Moderator", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("id");
b.Property<ulong>("DiscordId")
.HasColumnType("INTEGER")
.HasColumnName("discord_id");
b.Property<bool>("Sudoer")
.HasColumnType("INTEGER")
.HasColumnName("sudoer");
b.HasKey("Id")
.HasName("id");
b.HasIndex("DiscordId")
.IsUnique()
.HasDatabaseName("moderator_discord_id");
b.ToTable("moderator");
});
modelBuilder.Entity("CompatBot.Database.Piracystring", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("id");
b.Property<int>("Actions")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(11)
.HasColumnName("actions");
b.Property<byte>("Context")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue((byte)3)
.HasColumnName("context");
b.Property<string>("CustomMessage")
.HasColumnType("TEXT")
.HasColumnName("custom_message");
b.Property<bool>("Disabled")
.HasColumnType("INTEGER")
.HasColumnName("disabled");
b.Property<string>("ExplainTerm")
.HasColumnType("TEXT")
.HasColumnName("explain_term");
b.Property<string>("String")
.IsRequired()
.HasColumnType("varchar(255)")
.HasColumnName("string");
b.Property<string>("ValidatingRegex")
.HasColumnType("TEXT")
.HasColumnName("validating_regex");
b.HasKey("Id")
.HasName("id");
b.HasIndex("String")
.HasDatabaseName("piracystring_string");
b.ToTable("piracystring");
});
modelBuilder.Entity("CompatBot.Database.Stats", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("id");
b.Property<string>("Bucket")
.HasColumnType("TEXT")
.HasColumnName("bucket");
b.Property<string>("Category")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("category");
b.Property<long>("ExpirationTimestamp")
.HasColumnType("INTEGER")
.HasColumnName("expiration_timestamp");
b.Property<string>("Key")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("key");
b.Property<int>("Value")
.HasColumnType("INTEGER")
.HasColumnName("value");
b.HasKey("Id")
.HasName("id");
b.HasIndex("Category", "Bucket", "Key")
.IsUnique()
.HasDatabaseName("stats_category_bucket_key");
b.ToTable("stats");
});
modelBuilder.Entity("CompatBot.Database.SuspiciousString", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("id");
b.Property<string>("String")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("string");
b.HasKey("Id")
.HasName("id");
b.HasIndex("String")
.HasDatabaseName("suspicious_string_string");
b.ToTable("suspicious_string");
});
modelBuilder.Entity("CompatBot.Database.Warning", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("id");
b.Property<ulong>("DiscordId")
.HasColumnType("INTEGER")
.HasColumnName("discord_id");
b.Property<string>("FullReason")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("full_reason");
b.Property<ulong>("IssuerId")
.HasColumnType("INTEGER")
.HasColumnName("issuer_id");
b.Property<string>("Reason")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("reason");
b.Property<bool>("Retracted")
.HasColumnType("INTEGER")
.HasColumnName("retracted");
b.Property<ulong?>("RetractedBy")
.HasColumnType("INTEGER")
.HasColumnName("retracted_by");
b.Property<string>("RetractionReason")
.HasColumnType("TEXT")
.HasColumnName("retraction_reason");
b.Property<long?>("RetractionTimestamp")
.HasColumnType("INTEGER")
.HasColumnName("retraction_timestamp");
b.Property<long?>("Timestamp")
.HasColumnType("INTEGER")
.HasColumnName("timestamp");
b.HasKey("Id")
.HasName("id");
b.HasIndex("DiscordId")
.HasDatabaseName("warning_discord_id");
b.ToTable("warning");
});
modelBuilder.Entity("CompatBot.Database.WhitelistedInvite", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("id");
b.Property<ulong>("GuildId")
.HasColumnType("INTEGER")
.HasColumnName("guild_id");
b.Property<string>("InviteCode")
.HasColumnType("TEXT")
.HasColumnName("invite_code");
b.Property<string>("Name")
.HasColumnType("TEXT")
.HasColumnName("name");
b.HasKey("Id")
.HasName("id");
b.HasIndex("GuildId")
.IsUnique()
.HasDatabaseName("whitelisted_invite_guild_id");
b.ToTable("whitelisted_invites");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,45 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace CompatBot.Database.Migrations
{
public partial class AddStatsBucketColumn : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "stats_category_key",
table: "stats");
migrationBuilder.AddColumn<string>(
name: "bucket",
table: "stats",
type: "TEXT",
nullable: true);
migrationBuilder.CreateIndex(
name: "stats_category_bucket_key",
table: "stats",
columns: new[] { "category", "bucket", "key" },
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "stats_category_bucket_key",
table: "stats");
migrationBuilder.DropColumn(
name: "bucket",
table: "stats");
migrationBuilder.CreateIndex(
name: "stats_category_key",
table: "stats",
columns: new[] { "category", "key" },
unique: true);
}
}
}

View File

@ -5,6 +5,8 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace CompatBot.Database.Migrations
{
[DbContext(typeof(BotDb))]
@ -13,8 +15,7 @@ namespace CompatBot.Database.Migrations
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "5.0.5");
modelBuilder.HasAnnotation("ProductVersion", "6.0.6");
modelBuilder.Entity("CompatBot.Database.BotState", b =>
{
@ -289,6 +290,10 @@ namespace CompatBot.Database.Migrations
.HasColumnType("INTEGER")
.HasColumnName("id");
b.Property<string>("Bucket")
.HasColumnType("TEXT")
.HasColumnName("bucket");
b.Property<string>("Category")
.IsRequired()
.HasColumnType("TEXT")
@ -310,9 +315,9 @@ namespace CompatBot.Database.Migrations
b.HasKey("Id")
.HasName("id");
b.HasIndex("Category", "Key")
b.HasIndex("Category", "Bucket", "Key")
.IsUnique()
.HasDatabaseName("stats_category_key");
.HasDatabaseName("stats_category_bucket_key");
b.ToTable("stats");
});

View File

@ -11,12 +11,14 @@ namespace CompatBot.Database.Providers;
internal static class StatsStorage
{
internal static readonly TimeSpan CacheTime = TimeSpan.FromDays(1);
internal static readonly MemoryCache CmdStatCache = new(new MemoryCacheOptions { ExpirationScanFrequency = TimeSpan.FromDays(1) });
internal static readonly MemoryCache ExplainStatCache = new(new MemoryCacheOptions { ExpirationScanFrequency = TimeSpan.FromDays(1) });
internal static readonly MemoryCache GameStatCache = new(new MemoryCacheOptions { ExpirationScanFrequency = TimeSpan.FromDays(1) });
private static readonly TimeSpan CacheTime = TimeSpan.FromDays(1);
private static readonly MemoryCache CmdStatCache = new(new MemoryCacheOptions { ExpirationScanFrequency = TimeSpan.FromDays(1) });
private static readonly MemoryCache ExplainStatCache = new(new MemoryCacheOptions { ExpirationScanFrequency = TimeSpan.FromDays(1) });
private static readonly MemoryCache GameStatCache = new(new MemoryCacheOptions { ExpirationScanFrequency = TimeSpan.FromDays(1) });
private const char PrefixSeparator = '\0';
private static readonly SemaphoreSlim Barrier = new(1, 1);
private static readonly SemaphoreSlim BucketLock = new(1, 1);
private static readonly (string name, MemoryCache cache)[] AllCaches =
{
(nameof(CmdStatCache), CmdStatCache),
@ -24,6 +26,50 @@ internal static class StatsStorage
(nameof(GameStatCache), GameStatCache),
};
private static ((int y, int m, int d, int h) Key, string Value) bucketPrefix = ((0, 0, 0, 0), "");
private static string Prefix
{
get
{
var ts = DateTime.UtcNow;
var key = (ts.Year, ts.Month, ts.Day, ts.Hour);
if (bucketPrefix.Key == key)
return bucketPrefix.Value;
if (!BucketLock.Wait(0))
return bucketPrefix.Value;
bucketPrefix = (key, ts.ToString("yyyyMMddHH") + PrefixSeparator);
BucketLock.Release();
return bucketPrefix.Value;
}
}
public static void IncCmdStat(string qualifiedName) => IncStat(qualifiedName, CmdStatCache);
public static void IncExplainStat(string term) => IncStat(term, ExplainStatCache);
public static void IncGameStat(string title) => IncStat(title, GameStatCache);
private static void IncStat(string key, MemoryCache cache)
{
var bucketKey = Prefix + key;
cache.TryGetValue(bucketKey, out int stat);
cache.Set(bucketKey, ++stat, CacheTime);
}
public static List<(string name, int stat)> GetCmdStats() => GetStats(CmdStatCache);
public static List<(string name, int stat)> GetExplainStats() => GetStats(ExplainStatCache);
public static List<(string name, int stat)> GetGameStats() => GetStats(GameStatCache);
private static List<(string name, int stat)> GetStats(MemoryCache cache)
{
return cache.GetCacheKeys<string>()
.Select(c => (name: c.Split(PrefixSeparator, 2)[^1], stat: cache.Get(c) as int?))
.Where(s => s.stat.HasValue)
.GroupBy(s => s.name)
.Select(g => (name: g.Key, stat: (int)g.Sum(s => s.stat)!))
.OrderByDescending(s => s.stat)
.ToList();
}
public static async Task SaveAsync(bool wait = false)
{
if (await Barrier.WaitAsync(0).ConfigureAwait(false))
@ -32,21 +78,35 @@ internal static class StatsStorage
{
Config.Log.Debug("Got stats saving lock");
await using var db = new BotDb();
db.Stats.RemoveRange(db.Stats);
await db.SaveChangesAsync().ConfigureAwait(false);
foreach (var (category, cache) in AllCaches)
{
var entries = cache.GetCacheEntries<string>();
var savedKeys = new HashSet<string>();
foreach (var (key, value) in entries)
if (savedKeys.Add(key))
await db.Stats.AddAsync(new Stats
{
var keyParts = key.Split(PrefixSeparator, 2);
var bucket = keyParts.Length == 2 ? keyParts[0] : null;
var statKey = keyParts[^1];
var statValue = (int?)value?.Value ?? 0;
var ts = value?.AbsoluteExpiration?.ToUniversalTime().Ticks ?? 0;
var currentEntry = db.Stats.FirstOrDefault(e => e.Category == category && e.Bucket == bucket && e.Key == statKey);
if (currentEntry is null)
await db.Stats.AddAsync(new()
{
Category = category,
Bucket = bucket,
Key = statKey,
Value = statValue,
ExpirationTimestamp = ts
}).ConfigureAwait(false);
else
{
Category = category,
Key = key,
Value = (int?)value?.Value ?? 0,
ExpirationTimestamp = value?.AbsoluteExpiration?.ToUniversalTime().Ticks ?? 0
}).ConfigureAwait(false);
currentEntry.Value = statValue;
currentEntry.ExpirationTimestamp = ts;
}
}
else
Config.Log.Warn($"Somehow there's another '{key}' in the {category} cache");
}
@ -80,9 +140,19 @@ internal static class StatsStorage
{
var time = entry.ExpirationTimestamp.AsUtc();
if (time > now)
cache.Set(entry.Key, entry.Value, time);
{
var key = entry.Key;
if (entry.Bucket is { Length: > 0 } bucket)
key = bucket + PrefixSeparator + key;
cache.Set(key, entry.Value, time);
}
else
{
db.Stats.Remove(entry);
}
}
}
await db.SaveChangesAsync(Config.Cts.Token).ConfigureAwait(false);
}
public static async Task BackgroundSaveAsync()

View File

@ -32,7 +32,7 @@ internal static class IsTheGamePlayableHandler
public static async Task OnMessageCreated(DiscordClient c, MessageCreateEventArgs args)
{
if (DefaultHandlerFilter.IsFluff(args.Message))
if (DefaultHandlerFilter.IsFluff(args.Message) || args.Channel is null)
return;
#if !DEBUG
@ -125,10 +125,7 @@ internal static class IsTheGamePlayableHandler
return (null, null);
if (!string.IsNullOrEmpty(info.Title))
{
StatsStorage.GameStatCache.TryGetValue(info.Title, out int stat);
StatsStorage.GameStatCache.Set(info.Title, ++stat, StatsStorage.CacheTime);
}
StatsStorage.IncGameStat(info.Title);
return (code, info);
}
catch (Exception e)

View File

@ -102,10 +102,7 @@ internal static class TitleInfoFormatter
desc += " (cached)";
var cacheTitle = info.Title ?? gameTitle;
if (!string.IsNullOrEmpty(cacheTitle))
{
StatsStorage.GameStatCache.TryGetValue(cacheTitle, out int stat);
StatsStorage.GameStatCache.Set(cacheTitle, ++stat, StatsStorage.CacheTime);
}
StatsStorage.IncGameStat(cacheTitle);
var title = $"{productCodePart}{cacheTitle?.Trim(200)}{onlineOnlyPart}";
if (string.IsNullOrEmpty(title))
desc = "";
@ -143,8 +140,7 @@ internal static class TitleInfoFormatter
gameTitle = titleName;
if (!string.IsNullOrEmpty(gameTitle))
{
StatsStorage.GameStatCache.TryGetValue(gameTitle, out int stat);
StatsStorage.GameStatCache.Set(gameTitle, ++stat, StatsStorage.CacheTime);
StatsStorage.IncGameStat(gameTitle);
result.Title = $"{productCodePart}{gameTitle.Sanitize().Trim(200)}";
}
return result;