Skip to content

Commit

Permalink
#137 Proxy caches invalidation and tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
MonkAlex committed May 18, 2019
1 parent dbf64f8 commit e2d41d1
Show file tree
Hide file tree
Showing 8 changed files with 285 additions and 43 deletions.
28 changes: 27 additions & 1 deletion MangaReader.Core/Account/ProxySetting.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
using System;
using System.Diagnostics;
using System.Net;
using System.Threading.Tasks;
using MangaReader.Core.Entity;
using MangaReader.Core.Services;

namespace MangaReader.Core.Account
{
[DebuggerDisplay("Type = {SettingType}, Id = {Id}, User = {UserName}")]
public class ProxySetting : Entity.Entity
{
internal static readonly Lazy<IWebProxy> SystemProxy = new Lazy<IWebProxy>(() =>
Expand Down Expand Up @@ -34,12 +38,34 @@ public virtual IWebProxy GetProxy()
case ProxySettingType.Manual:
return new WebProxy(Address, true, null, new NetworkCredential(UserName, Password));
case ProxySettingType.Parent:
return MangaSettingCache.Get(typeof(IPlugin)).Proxy;
return MangaSettingCache.Get(MangaSettingCache.RootPluginType).Proxy;
default:
throw new ArgumentOutOfRangeException();
}
}

public override Task BeforeSave(ChangeTrackerArgs args)
{
if (!args.IsNewEntity && !args.CanAddEntities)
{
var typeState = args.GetPropertyState<ProxySettingType>(nameof(SettingType));
if (typeState.IsChanged)
MangaSettingCache.RevalidateSetting(this);
else
{
var userNameState = args.GetPropertyState<string>(nameof(UserName));
var passwordState = args.GetPropertyState<string>(nameof(Password));
var addressState = args.GetPropertyState<Uri>(nameof(Address));
if (userNameState.IsChanged || passwordState.IsChanged || addressState.IsChanged)
{
MangaSettingCache.RevalidateSetting(this);
}
}
}

return base.BeforeSave(args);
}

protected ProxySetting()
{
this.SettingType = ProxySettingType.NoProxy;
Expand Down
3 changes: 0 additions & 3 deletions MangaReader.Core/Convertation/Config/From47To48.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using MangaReader.Core.Convertation.Primitives;
using MangaReader.Core.NHibernate;
using MangaReader.Core.Services;
using MangaReader.Core.Services.Config;

namespace MangaReader.Core.Convertation.Config
{
Expand All @@ -26,8 +25,6 @@ protected override async Task ProtectedConvert(IProcess process)
}

await settings.SaveAll(context).ConfigureAwait(false);

await MangaSettingCache.RevalidateCache().ConfigureAwait(false);
}
}

Expand Down
17 changes: 13 additions & 4 deletions MangaReader.Core/Entity/ChangeTrackerArgs.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace MangaReader.Core.Entity
using System;

namespace MangaReader.Core.Entity
{
public class ChangeTrackerArgs
{
Expand All @@ -8,10 +10,17 @@ public class ChangeTrackerArgs
public readonly bool CanAddEntities;
public readonly bool IsNewEntity;

public PropertyChangeTracker<T> GetPropertyState<T>(string propertyName) where T : class
/// <summary>
/// Get property changes from session.
/// </summary>
/// <typeparam name="T">Type of property</typeparam>
/// <param name="propertyName">Property name. Use nameof() for support refactoring.</param>
/// <returns>Old-new value.</returns>
/// <remarks>For struct use nullable type for safe invoke. Or use it only for changes, not for first creating.</remarks>
public PropertyChangeTracker<T> GetPropertyState<T>(string propertyName)
{
var propertyIndex = System.Array.IndexOf(propertyNames, propertyName);
return new PropertyChangeTracker<T>(currentState[propertyIndex] as T, previousState?[propertyIndex] as T);
return new PropertyChangeTracker<T>((T)currentState[propertyIndex], (T)previousState?[propertyIndex]);
}

public void SetPropertyState(string propertyName, object value)
Expand All @@ -30,7 +39,7 @@ public ChangeTrackerArgs(object[] currentState, object[] previousState, string[]
}
}

public struct PropertyChangeTracker<T> where T : class
public struct PropertyChangeTracker<T>
{
public T Value;
public T OldValue;
Expand Down
24 changes: 21 additions & 3 deletions MangaReader.Core/Services/Config/DatabaseConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Linq;
using System.Threading.Tasks;
using MangaReader.Core.Account;
using MangaReader.Core.Entity;
using MangaReader.Core.NHibernate;

namespace MangaReader.Core.Services.Config
Expand Down Expand Up @@ -46,12 +47,25 @@ public Guid UniqueId
/// </summary>
public ProxySetting ProxySetting { get; set; }

public override Task BeforeSave(ChangeTrackerArgs args)
{
if (!args.IsNewEntity && !args.CanAddEntities)
{
var proxyState = args.GetPropertyState<ProxySetting>(nameof(ProxySetting));
if (proxyState.IsChanged)
MangaSettingCache.RevalidateSetting(MangaSettingCache.RootPluginType, proxyState.Value);
}

return base.BeforeSave(args);
}

/// <summary>
/// Создать дефолтные настройки для новых типов.
/// </summary>
/// <param name="context">Контекст подключения к БД.</param>
/// <param name="databaseConfig">Настройки приложения.</param>
/// <returns>Коллекция всех настроек.</returns>
private static async Task CreateDefaultMangaSettings(RepositoryContext context)
private static async Task CreateDefaultMangaSettings(RepositoryContext context, DatabaseConfig databaseConfig)
{
var settings = await context.Get<MangaSetting>().ToListAsync().ConfigureAwait(false);
var plugins = ConfigStorage.Plugins;
Expand All @@ -74,7 +88,11 @@ private static async Task CreateDefaultMangaSettings(RepositoryContext context)
settings.Add(setting);
}

await MangaSettingCache.RevalidateCache().ConfigureAwait(false);
MangaSettingCache.Set(new MangaSettingCache(databaseConfig));
foreach (var setting in settings)
{
MangaSettingCache.Set(new MangaSettingCache(setting));
}
}

private static async Task<List<ProxySetting>> CreateDefaultProxySettings(RepositoryContext context)
Expand Down Expand Up @@ -111,7 +129,7 @@ public static async Task Initialize()
config.ProxySetting = proxySettings.FirstOrDefault(s => s.SettingType == ProxySettingType.System);
await context.Save(config).ConfigureAwait(false);
}
await CreateDefaultMangaSettings(context).ConfigureAwait(false);
await CreateDefaultMangaSettings(context, config).ConfigureAwait(false);
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions MangaReader.Core/Services/MangaSetting.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,13 @@ public override async Task BeforeSave(ChangeTrackerArgs args)
}
}
}

if (!args.CanAddEntities)
{
var proxyState = args.GetPropertyState<ProxySetting>(nameof(ProxySetting));
if (proxyState.IsChanged)
MangaSettingCache.RevalidateSetting(MangaSettingCache.GetPluginType(this), proxyState.Value);
}
}
}

Expand Down
92 changes: 60 additions & 32 deletions MangaReader.Core/Services/MangaSettingCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Net;
using MangaReader.Core.Account;
using MangaReader.Core.NHibernate;
using MangaReader.Core.Services.Config;

namespace MangaReader.Core.Services
Expand All @@ -13,10 +12,30 @@ public class MangaSettingCache
{
public Type Plugin { get; set; }

public System.Net.IWebProxy Proxy { get; set; }
public IWebProxy Proxy { get; set; }

public int ProxySettingId { get; set; }

public bool IsParent { get; private set; }

public ProxySettingType SettingType { get; set; }

private void RefreshProperties(ProxySetting setting)
{
this.ProxySettingId = setting.Id;
this.Proxy = setting.GetProxy();
this.SettingType = setting.SettingType;
}

private static ConcurrentDictionary<Type, MangaSettingCache> caches = new ConcurrentDictionary<Type, MangaSettingCache>();

public static readonly Type RootPluginType = typeof(IPlugin);

public static Type GetPluginType(MangaSetting mangaSetting)
{
return ConfigStorage.Plugins.Single(p => p.MangaGuid == mangaSetting.Manga).GetType();
}

public static void Set(MangaSettingCache cache)
{
caches.AddOrUpdate(cache.Plugin, cache, (_, __) => cache);
Expand All @@ -30,42 +49,51 @@ public static MangaSettingCache Get(Type pluginType)
throw new KeyNotFoundException($"В кеше не найдено ничего с типом {pluginType.Name}");
}

public static void Clear()
internal static void RevalidateSetting(ProxySetting setting)
{
caches.Clear();
RevalidateSetting(null, setting);
}

public static async Task RevalidateCache()
internal static void RevalidateSetting(Type pluginType, ProxySetting setting)
{
Clear();
if (caches.Count == 0)
return;

var inCache = pluginType == null ?
caches.Values.Where(c => c.ProxySettingId == setting.Id).ToList() :
caches.Values.Where(c => c.Plugin == pluginType).ToList();

foreach (var cache in inCache.OrderByDescending(c => c.IsParent))
cache.RefreshProperties(setting);

using (var context = Repository.GetEntityContext())
Log.Add($"Applied {setting.SettingType} proxy to {string.Join(", ", inCache.Select(s => s.Plugin.Name))}");

// If any of setting cache is root - need recreate all 'child' caches.
if (inCache.Any(r => r.IsParent))
{
var config = await context.Get<DatabaseConfig>().SingleAsync().ConfigureAwait(false);
var parentSetting = config.ProxySetting;
if (parentSetting != null)
{
MangaSettingCache.Set(new MangaSettingCache()
{
Plugin = typeof(IPlugin),
Proxy = parentSetting.GetProxy()
});
}

var settings = await context.Get<MangaSetting>().Where(s => s.ProxySetting != null).ToListAsync().ConfigureAwait(false);
foreach (var setting in settings)
{
var plugin = ConfigStorage.Plugins.Single(p => p.MangaGuid == setting.Manga).GetType();
MangaSettingCache.Set(new MangaSettingCache
{
Plugin = plugin,
Proxy = setting.ProxySetting.SettingType == ProxySettingType.Parent ? Get(typeof(IPlugin)).Proxy : setting.ProxySetting.GetProxy()
});
}

foreach (var grouped in settings.GroupBy(s => s.ProxySetting.SettingType))
Log.Add($"Applied {grouped.Key} proxy to {string.Join(", ", grouped.Select(s => s.MangaName))}");
var childs = caches.Values.Where(c => c.SettingType == ProxySettingType.Parent).ToList();
foreach (var cache in childs)
cache.Proxy = setting.GetProxy();

Log.Add($"Applied {setting.SettingType} proxy to childs {string.Join(", ", childs.Select(s => s.Plugin.Name))}");
}
}

public MangaSettingCache(MangaSetting mangaSetting) : this(GetPluginType(mangaSetting), mangaSetting.ProxySetting)
{

}

public MangaSettingCache(DatabaseConfig config) : this(RootPluginType, config.ProxySetting)
{
IsParent = true;
}

public MangaSettingCache(Type pluginType, ProxySetting setting)
{
this.Plugin = pluginType;
this.RefreshProperties(setting);
this.IsParent = false;
}
}
}
Loading

0 comments on commit e2d41d1

Please sign in to comment.