From 121f3b973d477bc50c67a5e191562023a7fe5475 Mon Sep 17 00:00:00 2001 From: Keivan Beigi Date: Tue, 23 Jul 2013 17:35:35 -0700 Subject: [PATCH] added TTL to cached objects --- NzbDrone.Api/Config/NamingConfigResource.cs | 17 ++++ NzbDrone.Api/Config/NamingModule.cs | 33 ++++++++ NzbDrone.Api/Config/SettingsModule.cs | 57 +++---------- NzbDrone.Api/NzbDrone.Api.csproj | 2 + .../CacheTests/CachedFixture.cs | 62 ++++++-------- .../CacheTests/CachedManagerFixture.cs | 7 +- NzbDrone.Common/Cache/CacheManger.cs | 1 - NzbDrone.Common/Cache/Cached.cs | 84 +++++++++++-------- NzbDrone.Common/Cache/ICached.cs | 8 +- .../Configuration/ConfigFileProvider.cs | 26 +++--- .../Framework/MigrationController.cs | 4 +- 11 files changed, 160 insertions(+), 141 deletions(-) create mode 100644 NzbDrone.Api/Config/NamingConfigResource.cs create mode 100644 NzbDrone.Api/Config/NamingModule.cs diff --git a/NzbDrone.Api/Config/NamingConfigResource.cs b/NzbDrone.Api/Config/NamingConfigResource.cs new file mode 100644 index 000000000..af22a3f11 --- /dev/null +++ b/NzbDrone.Api/Config/NamingConfigResource.cs @@ -0,0 +1,17 @@ +using System; +using NzbDrone.Api.REST; + +namespace NzbDrone.Api.Config +{ + public class NamingConfigResource : RestResource + { + public Boolean IncludeEpisodeTitle { get; set; } + public Boolean ReplaceSpaces { get; set; } + public Boolean RenameEpisodes { get; set; } + public Int32 MultiEpisodeStyle { get; set; } + public Int32 NumberStyle { get; set; } + public String Separator { get; set; } + public Boolean IncludeQuality { get; set; } + public Boolean IncludeSeriesTitle { get; set; } + } +} \ No newline at end of file diff --git a/NzbDrone.Api/Config/NamingModule.cs b/NzbDrone.Api/Config/NamingModule.cs new file mode 100644 index 000000000..eb17a7c64 --- /dev/null +++ b/NzbDrone.Api/Config/NamingModule.cs @@ -0,0 +1,33 @@ +using FluentValidation; +using NzbDrone.Core.Organizer; + +namespace NzbDrone.Api.Config +{ + public class NamingModule : NzbDroneRestModule + { + private readonly INamingConfigService _namingConfigService; + + public NamingModule(INamingConfigService namingConfigService) + : base("config/naming") + { + _namingConfigService = namingConfigService; + GetResourceSingle = GetNamingConfig; + + UpdateResource = UpdateNamingConfig; + + SharedValidator.RuleFor(c => c.MultiEpisodeStyle).InclusiveBetween(0, 3); + SharedValidator.RuleFor(c => c.NumberStyle).InclusiveBetween(0, 3); + SharedValidator.RuleFor(c => c.Separator).Matches(@"\s|\s\-\s|\."); + } + + private NamingConfigResource UpdateNamingConfig(NamingConfigResource resource) + { + return ToResource(_namingConfigService.Save, resource); + } + + private NamingConfigResource GetNamingConfig() + { + return ToResource(_namingConfigService.GetConfig); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Api/Config/SettingsModule.cs b/NzbDrone.Api/Config/SettingsModule.cs index 8b670c014..d4135393f 100644 --- a/NzbDrone.Api/Config/SettingsModule.cs +++ b/NzbDrone.Api/Config/SettingsModule.cs @@ -2,54 +2,10 @@ using System.Collections.Generic; using Nancy; using NzbDrone.Api.Extensions; -using NzbDrone.Api.REST; using NzbDrone.Core.Configuration; -using NzbDrone.Core.Organizer; -using FluentValidation; namespace NzbDrone.Api.Config { - public class NamingModule : NzbDroneRestModule - { - private readonly INamingConfigService _namingConfigService; - - public NamingModule(INamingConfigService namingConfigService) - : base("config/naming") - { - _namingConfigService = namingConfigService; - GetResourceSingle = GetNamingConfig; - - UpdateResource = UpdateNamingConfig; - - SharedValidator.RuleFor(c => c.MultiEpisodeStyle).InclusiveBetween(0, 3); - SharedValidator.RuleFor(c => c.NumberStyle).InclusiveBetween(0, 3); - SharedValidator.RuleFor(c => c.Separator).Matches(@"\s|\s\-\s|\."); - } - - private NamingConfigResource UpdateNamingConfig(NamingConfigResource resource) - { - return ToResource(_namingConfigService.Save, resource); - } - - private NamingConfigResource GetNamingConfig() - { - return ToResource(_namingConfigService.GetConfig); - } - } - - public class NamingConfigResource : RestResource - { - public Boolean IncludeEpisodeTitle { get; set; } - public Boolean ReplaceSpaces { get; set; } - public Boolean RenameEpisodes { get; set; } - public Int32 MultiEpisodeStyle { get; set; } - public Int32 NumberStyle { get; set; } - public String Separator { get; set; } - public Boolean IncludeQuality { get; set; } - public Boolean IncludeSeriesTitle { get; set; } - } - - public class SettingsModule : NzbDroneApiModule { private readonly IConfigService _configService; @@ -65,6 +21,19 @@ public SettingsModule(IConfigService configService, IConfigFileProvider configFi Get["/host"] = x => GetHostSettings(); Post["/host"] = x => SaveHostSettings(); + + Get["/log"] = x => GetLogSettings(); + Post["/log"] = x => SaveLogSettings(); + } + + private Response SaveLogSettings() + { + throw new NotImplementedException(); + } + + private Response GetLogSettings() + { + throw new NotImplementedException(); } private Response SaveHostSettings() diff --git a/NzbDrone.Api/NzbDrone.Api.csproj b/NzbDrone.Api/NzbDrone.Api.csproj index aaa3a3c8a..6288bd210 100644 --- a/NzbDrone.Api/NzbDrone.Api.csproj +++ b/NzbDrone.Api/NzbDrone.Api.csproj @@ -101,6 +101,8 @@ + + diff --git a/NzbDrone.Common.Test/CacheTests/CachedFixture.cs b/NzbDrone.Common.Test/CacheTests/CachedFixture.cs index 68f6d54ef..647e4fb96 100644 --- a/NzbDrone.Common.Test/CacheTests/CachedFixture.cs +++ b/NzbDrone.Common.Test/CacheTests/CachedFixture.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Threading; using FluentAssertions; using NUnit.Framework; using NzbDrone.Common.Cache; @@ -28,8 +29,6 @@ public void should_call_function_once() } - - [Test] public void multiple_calls_should_return_same_result() { @@ -37,43 +36,16 @@ public void multiple_calls_should_return_same_result() var second = _cachedString.Get("Test", _worker.GetString); first.Should().Be(second); - } - [Test] - public void should_remove_value_from_set() - { - _cachedString.Get("Test", _worker.GetString); - - _cachedString.Remove("Test"); - - _cachedString.Get("Test", _worker.GetString); - - - _worker.HitCount.Should().Be(2); - - } - - [Test] - public void remove_none_existing_should_break_things() - { - _cachedString.Remove("Test"); - } - - [Test] - public void get_without_callback_should_throw_on_invalid_key() - { - Assert.Throws(() => _cachedString.Get("InvalidKey")); - } - [Test] public void should_be_able_to_update_key() { _cachedString.Set("Key", "Old"); _cachedString.Set("Key", "New"); - _cachedString.Get("Key").Should().Be("New"); + _cachedString.Find("Key").Should().Be("New"); } [Test] @@ -85,14 +57,34 @@ public void should_store_null() for (int i = 0; i < 10; i++) { _cachedString.Get("key", () => - { - hitCount++; - return null; - }); + { + hitCount++; + return null; + }); } hitCount.Should().Be(1); } + + [Test] + public void should_honor_ttl() + { + int hitCount = 0; + _cachedString = new Cached(); + + for (int i = 0; i < 100; i++) + { + _cachedString.Get("key", () => + { + hitCount++; + return null; + }, TimeSpan.FromMilliseconds(200)); + + Thread.Sleep(10); + } + + hitCount.Should().BeInRange(4, 6); + } } public class Worker diff --git a/NzbDrone.Common.Test/CacheTests/CachedManagerFixture.cs b/NzbDrone.Common.Test/CacheTests/CachedManagerFixture.cs index 57a220d9c..1f424d4a1 100644 --- a/NzbDrone.Common.Test/CacheTests/CachedManagerFixture.cs +++ b/NzbDrone.Common.Test/CacheTests/CachedManagerFixture.cs @@ -7,7 +7,7 @@ namespace NzbDrone.Common.Test.CacheTests { [TestFixture] - public class CachedManagerFixture:TestBase + public class CachedManagerFixture : TestBase { [Test] public void should_return_proper_type_of_cache() @@ -17,7 +17,6 @@ public void should_return_proper_type_of_cache() result.Should().BeOfType>(); } - [Test] public void multiple_calls_should_get_the_same_cache() { @@ -26,9 +25,5 @@ public void multiple_calls_should_get_the_same_cache() result1.Should().BeSameAs(result2); } - - - - } } \ No newline at end of file diff --git a/NzbDrone.Common/Cache/CacheManger.cs b/NzbDrone.Common/Cache/CacheManger.cs index beba81651..d264eeb40 100644 --- a/NzbDrone.Common/Cache/CacheManger.cs +++ b/NzbDrone.Common/Cache/CacheManger.cs @@ -8,7 +8,6 @@ public interface ICacheManger { ICached GetCache(Type host, string name); ICached GetCache(Type host); - //ICollection> Caches { get;} void Clear(); ICollection Caches { get; } } diff --git a/NzbDrone.Common/Cache/Cached.cs b/NzbDrone.Common/Cache/Cached.cs index de5680b8e..eef89f317 100644 --- a/NzbDrone.Common/Cache/Cached.cs +++ b/NzbDrone.Common/Cache/Cached.cs @@ -1,83 +1,99 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Linq; using NzbDrone.Common.EnsureThat; namespace NzbDrone.Common.Cache { + public class Cached : ICached { - private readonly ConcurrentDictionary _store; + private class CacheItem + { + public T Object { get; private set; } + public DateTime? ExpiryTime { get; private set; } + + public CacheItem(T obj, TimeSpan? lifetime = null) + { + Object = obj; + if (lifetime.HasValue) + { + ExpiryTime = DateTime.UtcNow + lifetime.Value; + } + } + + public bool IsExpired() + { + return ExpiryTime.HasValue && ExpiryTime.Value < DateTime.UtcNow; + } + } + + private readonly ConcurrentDictionary _store; public Cached() { - _store = new ConcurrentDictionary(); + _store = new ConcurrentDictionary(); } - public void Set(string key, T value) + public void Set(string key, T value, TimeSpan? lifetime = null) { Ensure.That(() => key).IsNotNullOrWhiteSpace(); - _store[key] = value; - } - - public T Get(string key) - { - return Get(key, () => { throw new KeyNotFoundException(key); }); + _store[key] = new CacheItem(value, lifetime); } public T Find(string key) { - T value; + CacheItem value; _store.TryGetValue(key, out value); - return value; + + if (value == null) + { + return default(T); + } + + if (value.IsExpired()) + { + _store.TryRemove(key, out value); + return default(T); + } + + return value.Object; } - public T Get(string key, Func function) + public T Get(string key, Func function, TimeSpan? lifeTime = null) { Ensure.That(() => key).IsNotNullOrWhiteSpace(); + CacheItem cacheItem; T value; - if (!_store.TryGetValue(key, out value)) + if (!_store.TryGetValue(key, out cacheItem) || cacheItem.IsExpired()) { value = function(); - Set(key, value); + Set(key, value, lifeTime); + } + else + { + value = cacheItem.Object; } return value; } - public bool ContainsKey(string key) - { - Ensure.That(() => key).IsNotNullOrWhiteSpace(); - return _store.ContainsKey(key); - } public void Clear() { _store.Clear(); } - public void Remove(string key) - { - Ensure.That(() => key).IsNotNullOrWhiteSpace(); - T value; - _store.TryRemove(key, out value); - } - public ICollection Values { get { - return _store.Values; - } - } - public ICollection Keys - { - get - { - return _store.Keys; + return _store.Values.Select(c => c.Object).ToList(); } } + } } \ No newline at end of file diff --git a/NzbDrone.Common/Cache/ICached.cs b/NzbDrone.Common/Cache/ICached.cs index 599479f7c..3708b72af 100644 --- a/NzbDrone.Common/Cache/ICached.cs +++ b/NzbDrone.Common/Cache/ICached.cs @@ -5,19 +5,15 @@ namespace NzbDrone.Common.Cache { public interface ICached { - bool ContainsKey(string key); void Clear(); - void Remove(string key); } public interface ICached : ICached { - void Set(string key, T value); - T Get(string key, Func function); - T Get(string key); + void Set(string key, T value, TimeSpan? lifetime = null); + T Get(string key, Func function, TimeSpan? lifeTime = null); T Find(string key); ICollection Values { get; } - ICollection Keys { get; } } } \ No newline at end of file diff --git a/NzbDrone.Core/Configuration/ConfigFileProvider.cs b/NzbDrone.Core/Configuration/ConfigFileProvider.cs index d93decf86..71e4e8aba 100644 --- a/NzbDrone.Core/Configuration/ConfigFileProvider.cs +++ b/NzbDrone.Core/Configuration/ConfigFileProvider.cs @@ -118,25 +118,25 @@ public T GetValueEnum(string key, T defaultValue) public string GetValue(string key, object defaultValue) { return _cache.Get(key, () => - { - EnsureDefaultConfigFile(); + { + EnsureDefaultConfigFile(); - var xDoc = XDocument.Load(_configFile); - var config = xDoc.Descendants("Config").Single(); + var xDoc = XDocument.Load(_configFile); + var config = xDoc.Descendants("Config").Single(); - var parentContainer = config; + var parentContainer = config; - var valueHolder = parentContainer.Descendants(key).ToList(); + var valueHolder = parentContainer.Descendants(key).ToList(); - if (valueHolder.Count() == 1) - return valueHolder.First().Value; + if (valueHolder.Count() == 1) + return valueHolder.First().Value; - //Save the value - SetValue(key, defaultValue); + //Save the value + SetValue(key, defaultValue); - //return the default value - return defaultValue.ToString(); - }); + //return the default value + return defaultValue.ToString(); + }); } public void SetValue(string key, object value) diff --git a/NzbDrone.Core/Datastore/Migration/Framework/MigrationController.cs b/NzbDrone.Core/Datastore/Migration/Framework/MigrationController.cs index 0c44259b1..e2a0480f2 100644 --- a/NzbDrone.Core/Datastore/Migration/Framework/MigrationController.cs +++ b/NzbDrone.Core/Datastore/Migration/Framework/MigrationController.cs @@ -28,10 +28,10 @@ public void MigrateToLatest(string connectionString, MigrationType migrationType { lock (MigrationCache) { - _announcer.Heading("Migrating " + connectionString); - if (MigrationCache.Contains(connectionString.ToLower())) return; + _announcer.Heading("Migrating " + connectionString); + var assembly = Assembly.GetExecutingAssembly(); var migrationContext = new RunnerContext(_announcer)