From 4a4ce10c04e0660b213edd61bb6d247caf62833b Mon Sep 17 00:00:00 2001 From: Sergey Gut Date: Tue, 3 Mar 2020 11:36:00 +0400 Subject: [PATCH 1/2] Initial work on cache --- Core/Cache/ICacheStorage.cs | 10 ++++ Core/Cache/MemoryCache.cs | 93 +++++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 Core/Cache/ICacheStorage.cs create mode 100644 Core/Cache/MemoryCache.cs diff --git a/Core/Cache/ICacheStorage.cs b/Core/Cache/ICacheStorage.cs new file mode 100644 index 0000000..016f554 --- /dev/null +++ b/Core/Cache/ICacheStorage.cs @@ -0,0 +1,10 @@ +using System; + +namespace Kruzya.TelegramBot.Core.Cache +{ + public interface ICacheStorage + { + public bool TryGetValue(TKey key, ref TValue value); + public void SetValue(TKey key, TValue value, int timeToLive); + } +} \ No newline at end of file diff --git a/Core/Cache/MemoryCache.cs b/Core/Cache/MemoryCache.cs new file mode 100644 index 0000000..b4afc69 --- /dev/null +++ b/Core/Cache/MemoryCache.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace Kruzya.TelegramBot.Core.Cache +{ + public class MemoryCache : ICacheStorage + { + /// + /// Represents a cache entry in internal dictionary. + /// + /// + internal struct CacheEntry + { + public TVal Value; + public DateTime Created; + public int ExpiresAt; + + public bool IsExpired() + { + if (ExpiresAt == Timeout.Infinite) + { + return false; + } + + return (Created.AddSeconds(ExpiresAt)) < DateTime.Now; + } + } + + /// + /// + /// + private Dictionary> dict = new Dictionary>(); + + /// + /// The TTL for all entries if not set. + /// + private int _defaultTtl; + + public MemoryCache() : this(Timeout.Infinite) + { + } + + public MemoryCache(int defaultTtl) + { + _defaultTtl = defaultTtl; + } + + public bool TryGetValue(TKey key, ref TValue value) + { + if (dict.ContainsKey(key)) + { + return false; + } + + CacheEntry temp; + if (!dict.TryGetValue(key, out temp)) + { + return false; + } + + if (temp.IsExpired()) + { + dict.Remove(key); + return false; + } + + value = temp.Value; + return true; + } + + public void SetValue(TKey key, TValue value) + { + SetValue(key, value, _defaultTtl); + } + + public void SetValue(TKey key, TValue value, int timeToLive) + { + if (dict.ContainsKey(key)) + { + dict.Remove(key); + } + + dict.Add(key, new CacheEntry() + { + Created = DateTime.Now, + ExpiresAt = timeToLive, + Value = value + }); + } + } +} \ No newline at end of file From 94de8d5894a257c89155d9208a5b4a3247be5760 Mon Sep 17 00:00:00 2001 From: Kruzya Date: Tue, 3 Mar 2020 12:32:09 +0400 Subject: [PATCH 2/2] :construction: Tests for MemoryCache --- Core/Cache/ICacheStorage.cs | 2 +- Core/Cache/MemoryCache.cs | 6 +++-- Kruzya.TelegramBot.sln | 6 +++++ tests/MemoryCache.cs | 47 +++++++++++++++++++++++++++++++++++++ tests/Tests.csproj | 23 ++++++++++++++++++ 5 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 tests/MemoryCache.cs create mode 100644 tests/Tests.csproj diff --git a/Core/Cache/ICacheStorage.cs b/Core/Cache/ICacheStorage.cs index 016f554..0983354 100644 --- a/Core/Cache/ICacheStorage.cs +++ b/Core/Cache/ICacheStorage.cs @@ -4,7 +4,7 @@ namespace Kruzya.TelegramBot.Core.Cache { public interface ICacheStorage { - public bool TryGetValue(TKey key, ref TValue value); + public bool TryGetValue(TKey key, out TValue value); public void SetValue(TKey key, TValue value, int timeToLive); } } \ No newline at end of file diff --git a/Core/Cache/MemoryCache.cs b/Core/Cache/MemoryCache.cs index b4afc69..baf063f 100644 --- a/Core/Cache/MemoryCache.cs +++ b/Core/Cache/MemoryCache.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; +using SQLitePCL; namespace Kruzya.TelegramBot.Core.Cache { @@ -47,9 +48,10 @@ namespace Kruzya.TelegramBot.Core.Cache _defaultTtl = defaultTtl; } - public bool TryGetValue(TKey key, ref TValue value) + public bool TryGetValue(TKey key, out TValue value) { - if (dict.ContainsKey(key)) + value = default; + if (!dict.ContainsKey(key)) { return false; } diff --git a/Kruzya.TelegramBot.sln b/Kruzya.TelegramBot.sln index 4e5a92d..05b2821 100644 --- a/Kruzya.TelegramBot.sln +++ b/Kruzya.TelegramBot.sln @@ -8,6 +8,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CombotAntiSpam", "modules\C EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RichSiteSummary", "modules\RichSiteSummary\RichSiteSummary.csproj", "{C50D64C3-204B-4B58-927B-C8D759787252}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "tests\Tests.csproj", "{5C62CA70-F270-4029-953E-458623C31A3B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -26,6 +28,10 @@ Global {C50D64C3-204B-4B58-927B-C8D759787252}.Debug|Any CPU.Build.0 = Debug|Any CPU {C50D64C3-204B-4B58-927B-C8D759787252}.Release|Any CPU.ActiveCfg = Release|Any CPU {C50D64C3-204B-4B58-927B-C8D759787252}.Release|Any CPU.Build.0 = Release|Any CPU + {5C62CA70-F270-4029-953E-458623C31A3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5C62CA70-F270-4029-953E-458623C31A3B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5C62CA70-F270-4029-953E-458623C31A3B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5C62CA70-F270-4029-953E-458623C31A3B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {F1E1FBB7-ABA4-4304-9A42-D4975822B002} = {C7821F15-DEDD-474F-A575-A296D4B58F10} diff --git a/tests/MemoryCache.cs b/tests/MemoryCache.cs new file mode 100644 index 0000000..5c3cb2d --- /dev/null +++ b/tests/MemoryCache.cs @@ -0,0 +1,47 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Kruzya.TelegramBot.Core.Cache; +using NUnit.Framework; + +namespace Kruzya.TelegramBot.Core.Tests +{ + public class MemoryCache + { + [Test] + public void GetSetItem() + { + var cache = new MemoryCache(); + + int id; + Assert.IsFalse(cache.TryGetValue("user", out id), "GetValue() returned something"); + + id = 5; + cache.SetValue("user", id); + + id = 10; + Assert.IsTrue(cache.TryGetValue("user", out id), "GetValue() doesn't returned any value"); + Assert.IsTrue(id == 5, "GetValue() returned don't expected value"); + } + + [Test] + public void TimeToLive() + { + var cache = new MemoryCache(); + cache.SetValue("user", 5, 1); + cache.SetValue("admin", 10, 2); + cache.SetValue("manager", 15, Timeout.Infinite); + + int id; + Thread.Sleep(TimeSpan.FromSeconds(1)); + Assert.IsFalse(cache.TryGetValue("user", out id), "GetValue() returned expired entry (user)."); + Assert.IsTrue(cache.TryGetValue("admin", out id), "GetValue() doesn't returned live entry."); + Assert.IsTrue(id == 10, "GetValue() returned unexpected entry."); + + Thread.Sleep(TimeSpan.FromSeconds(1)); + Assert.IsFalse(cache.TryGetValue("admin", out id), "GetValue() returned expired entry (admin)."); + Assert.IsTrue(cache.TryGetValue("manager", out id), "GetValue() doesn't returned live entry."); + Assert.IsTrue(id == 15, "GetValue() returned unexpected entry."); + } + } +} \ No newline at end of file diff --git a/tests/Tests.csproj b/tests/Tests.csproj new file mode 100644 index 0000000..fab280a --- /dev/null +++ b/tests/Tests.csproj @@ -0,0 +1,23 @@ + + + + netcoreapp3.1 + + false + + TelegramBot.Test + + Kruzya.TelegramBot.Core.Tests + + + + + + + + + + + + +