Skip to content

Commit

Permalink
Many cache fixes and improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
aritchie committed Jun 17, 2024
1 parent 5d57de5 commit 6be99a7
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 144 deletions.
4 changes: 4 additions & 0 deletions src/Shiny.Mediator.Maui/MauiMediatorExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Microsoft.Extensions.DependencyInjection.Extensions;
using Shiny.Mediator.Maui;
using Shiny.Mediator.Maui.Services;
using Shiny.Mediator.Middleware;

namespace Shiny.Mediator;
Expand Down Expand Up @@ -51,6 +53,8 @@ public static ShinyConfigurator AddMainThreadEventMiddleware(this ShinyConfigura

public static ShinyConfigurator AddCacheMiddleware(this ShinyConfigurator cfg)
{
cfg.Services.TryAddSingleton<CacheManager>();
cfg.Services.AddSingletonAsImplementedInterfaces<CacheHandlers>();
cfg.AddOpenRequestMiddleware(typeof(CacheRequestMiddleware<,>));
return cfg;
}
Expand Down
18 changes: 18 additions & 0 deletions src/Shiny.Mediator.Maui/Middleware/CacheHandlers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Shiny.Mediator.Maui.Services;

namespace Shiny.Mediator.Middleware;

public class CacheHandlers(CacheManager cacheManager) : IRequestHandler<FlushAllCacheRequest>, IRequestHandler<FlushCacheItemRequest>
{
public Task Handle(FlushAllCacheRequest request, CancellationToken cancellationToken)
{
cacheManager.FlushAllCache();
return Task.CompletedTask;
}

public Task Handle(FlushCacheItemRequest request, CancellationToken cancellationToken)
{
cacheManager.RemoveCacheItem(request.Request);
return Task.CompletedTask;
}
}
154 changes: 10 additions & 144 deletions src/Shiny.Mediator.Maui/Middleware/CacheRequestMiddleware.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
using System.Text.Json;
using Shiny.Mediator.Maui.Services;

namespace Shiny.Mediator.Middleware;


public class CacheRequestMiddleware<TRequest, TResult>(
IConnectivity connectivity,
IFileSystem fileSystem
) : IRequestMiddleware<TRequest, TResult>
// IRequestHandler<FlushAllCacheRequest>,
// IRequestHandler<FlushCacheItemRequest>
public class CacheRequestMiddleware<TRequest, TResult>(CacheManager cacheManager) : IRequestMiddleware<TRequest, TResult>
{
readonly Dictionary<string, CachedItem<object>> memCache = new();


public async Task<TResult> Process(
TRequest request,
RequestHandlerDelegate<TResult> next,
Expand All @@ -28,144 +20,23 @@ CancellationToken cancellationToken
if (cfg == null)
return await next().ConfigureAwait(false);

var result = await this.Process(cfg, request, next, requestHandler, cancellationToken).ConfigureAwait(false);
var result = await this.Process(cfg, request, next).ConfigureAwait(false);
return result;
}


public virtual async Task<TResult> Process(
public virtual Task<TResult> Process(
CacheAttribute cfg,
TRequest request,
RequestHandlerDelegate<TResult> next,
IRequestHandler requestHandler,
CancellationToken cancellationToken
RequestHandlerDelegate<TResult> next
)
{
var result = default(TResult);
var connected = connectivity.NetworkAccess == NetworkAccess.Internet;
if (cfg.OnlyForOffline && connected)
{
result = await this.GetAndStore(request, next, cfg).ConfigureAwait(false);
return result;
}

var item = this.GetFromStore(request, cfg);
if (item == null)
{
if (connected)
result = await this.GetAndStore(request, next, cfg).ConfigureAwait(false);
}
else if (this.IsExpired(item, cfg))
{
result = item.Value;
if (connected)
result = await this.GetAndStore(request, next, cfg).ConfigureAwait(false);
}
else
result = item.Value;

return result;
}


protected virtual bool IsExpired(CachedItem<TResult> item, CacheAttribute cfg)
{
if (cfg.MaxAgeSeconds <= 0)
return false;

var expiry = item.CreatedOn.Add(TimeSpan.FromSeconds(cfg.MaxAgeSeconds));
var expired = expiry < DateTimeOffset.UtcNow;
return expired;
}


protected virtual async Task<TResult> GetAndStore(TRequest request, RequestHandlerDelegate<TResult> next, CacheAttribute cfg)
{
var result = await next().ConfigureAwait(false);
this.Store(request, result, cfg);
return result;
}


protected virtual string GetCacheKey(TRequest request)
{
if (request is ICacheItem item)
return item.CacheKey;

var key = $"{typeof(TRequest).Namespace}.{typeof(TRequest).Name}";
return key;
}


protected virtual string GetCacheFilePath(TRequest request)
{
var key = this.GetCacheKey(request);
var path = Path.Combine(fileSystem.CacheDirectory, $"{key}.cache");
return path;
}


protected virtual void Store(TRequest request, TResult result, CacheAttribute cfg)
{
switch (cfg.Storage)
{
case StoreType.File:
var path = this.GetCacheFilePath(request);
var json = JsonSerializer.Serialize(result);
File.WriteAllText(path, json);
break;

case StoreType.Memory:
var key = this.GetCacheKey(request);
lock (this.memCache)
{
this.memCache[key] = new CachedItem<object>(
DateTimeOffset.UtcNow,
result!
);
}
break;
}
}


protected virtual CachedItem<TResult>? GetFromStore(TRequest request, CacheAttribute cfg)
{
CachedItem<TResult>? returnValue = null;

switch (cfg.Storage)
{
case StoreType.File:
var path = this.GetCacheFilePath(request);
if (File.Exists(path))
{
var json = File.ReadAllText(path);
var obj = JsonSerializer.Deserialize<TResult>(json)!;
var createdOn = File.GetCreationTimeUtc(path);
returnValue = new CachedItem<TResult>(createdOn, obj);
}
break;


case StoreType.Memory:
var key = this.GetCacheKey(request);
lock (this.memCache)
{
if (this.memCache.ContainsKey(key))
{
var item = this.memCache[key];
returnValue = new CachedItem<TResult>(item.CreatedOn, (TResult)item.Value);
}
}
break;
}

return returnValue;
}
=> cacheManager.CacheOrGet<TResult>(
cfg,
request,
async () => await next().ConfigureAwait(false)
);
}

// public record FlushCacheItemRequest(Type RequestType) : IRequest;
// public record FlushAllCacheRequest : IRequest;

/// <summary>
/// Implementing this interface will allow you to create your own cache key, otherwise the cache key is based on the name
Expand All @@ -176,8 +47,3 @@ public interface ICacheItem
string CacheKey { get; }
}

public record CachedItem<T>(
DateTimeOffset CreatedOn,
T Value
);

4 changes: 4 additions & 0 deletions src/Shiny.Mediator.Maui/Models.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
namespace Shiny.Mediator;

public record FlushAllCacheRequest : IRequest;
public record FlushCacheItemRequest(object Request) : IRequest;
Loading

0 comments on commit 6be99a7

Please sign in to comment.