Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 42 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,6 @@ Or with overriddes for specifying different colors and size:
```
The defaults if nothing else is specified is of course the Geta colors as seen in the screenshot.

The images that gets generated are cached in [appDataPath]\thumb_cache\

## Configuration

For the ConentTypeIcons to work, you have to call `AddContentTypeIcons` extension method in `Startup.ConfigureServices` method. This method provides a configuration of default values and allows to enable tree icon feature. Below is a code with all possible configuration options:
Expand Down Expand Up @@ -165,7 +163,48 @@ services.AddContentTypeIcons(

### Caching

All generated icon images are stored in the cache folder and then served instead of regenerating those. A default cache folder can be changed by configuring the `CachePath` property.
All generated icon images are cached and served on subsequent requests instead of being regenerated. The default cache provider stores images in memory with a sliding expiration (default 24 hours, configurable via `InMemoryCacheSlidingExpiration`).

#### Using the disk cache provider

To persist cached images to disk instead, call `SetCacheProvider<T>()` after `AddContentTypeIcons`:

```cs
services.AddContentTypeIcons(x =>
{
x.EnableTreeIcons = true;
})
.SetCacheProvider<DiskIconCacheProvider>();
```

#### Creating a custom cache provider

Implement `IIconCacheProvider` and register it the same way:

```cs
public class MyCustomCacheProvider : IIconCacheProvider
{
public void Initialize() { /* called once at startup */ }

public bool TryGet(string key, out Image image)
{
// return true and set image if found, otherwise false
}

public void Set(string key, Image image)
{
// store the image under key
}
}

// Registration
services.AddContentTypeIcons(x => { ... })
.SetCacheProvider<MyCustomCacheProvider>();
```

The `key` is a deterministic filename (e.g. `abc123.png`) derived from the icon settings. Custom providers are resolved via DI, so constructor dependencies are supported.

The disk cache folder can be changed via the `CachePath` option (only relevant when using `DiskIconCacheProvider`).

## Changelog

Expand Down
14 changes: 14 additions & 0 deletions sandbox/Alloy/AlloyMvcTemplates.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerfileContext>..\..</DockerfileContext>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BuildBundlerMinifier" Version="$(BuildBundlerMinifier)" />
Expand All @@ -28,4 +29,17 @@
<ItemGroup>
<ProjectReference Include="..\..\src\Geta.Optimizely.ContentTypeIcons\Geta.Optimizely.ContentTypeIcons.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="App_Data\thumb_cache\" />
</ItemGroup>

<!-- Copy the plugin zip from the plugin's build output into the sandbox modules folder after build -->
<Target Name="CopyPluginZip" AfterTargets="Build">
<ItemGroup>
<PluginZip Include="..\..\src\Geta.Optimizely.ContentTypeIcons\bin\$(Configuration)\$(TargetFramework)\Geta.Optimizely.ContentTypeIcons.zip" />
</ItemGroup>
<MakeDir Directories="$(MSBuildProjectDirectory)\modules\_protected\Geta.Optimizely.ContentTypeIcons" />
<Copy SourceFiles="@(PluginZip)" DestinationFolder="$(MSBuildProjectDirectory)\modules\_protected\Geta.Optimizely.ContentTypeIcons\" />
</Target>

</Project>
8 changes: 5 additions & 3 deletions sandbox/Alloy/Models/Pages/SearchPage.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations;
using AlloyTemplates.Models.Blocks;
using EPiServer.Core;
using EPiServer.DataAbstraction;
using EPiServer.DataAnnotations;
using Geta.Optimizely.ContentTypeIcons;
using Geta.Optimizely.ContentTypeIcons.Attributes;

namespace AlloyTemplates.Models.Pages
{
Expand All @@ -12,7 +14,7 @@ namespace AlloyTemplates.Models.Pages
[SiteContentType(
GUID = "AAC25733-1D21-4F82-B031-11E626C91E30",
GroupName = Global.GroupNames.Specialized)]
[SiteImageUrl]
[ContentTypeIcon(FontAwesome7Solid.CartFlatbedSuitcase, Rotations.None)]
public class SearchPage : SitePageData, IHasRelatedContent, ISearchPage
{
[Display(
Expand All @@ -22,4 +24,4 @@ public class SearchPage : SitePageData, IHasRelatedContent, ISearchPage
[AllowedTypes(new[] { typeof(IContentData) }, new[] { typeof(JumbotronBlock) })]
public virtual ContentArea RelatedContentArea { get; set; }
}
}
}
13 changes: 7 additions & 6 deletions sandbox/Alloy/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
using System.IO;
using AlloyMvcTemplates;
using AlloyMvcTemplates.Extensions;
using AlloyMvcTemplates.Infrastructure;
using EPiServer.Authorization;
using EPiServer.Cms.UI.AspNetIdentity;
using EPiServer.Data;
using EPiServer.Framework.Web.Resources;
using EPiServer.Scheduler;
using EPiServer.ServiceLocation;
using EPiServer.Web.Routing;
using Geta.Optimizely.ContentTypeIcons.Caching;
using Geta.Optimizely.ContentTypeIcons.Infrastructure.Configuration;
using Geta.Optimizely.ContentTypeIcons.Infrastructure.Initialization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.IO;
using AlloyMvcTemplates;
using EPiServer.Authorization;
using Geta.Optimizely.ContentTypeIcons.Infrastructure.Configuration;
using Geta.Optimizely.ContentTypeIcons.Infrastructure.Initialization;

namespace EPiServer.Templates.Alloy.Mvc
{
Expand Down Expand Up @@ -60,7 +61,7 @@ public void ConfigureServices(IServiceCollection services)
x.FontSize = 40;
x.CachePath = "[appDataPath]\\thumb_cache\\";
x.CustomFontPath = "[appDataPath]\\fonts\\";
});
}).SetCacheProvider<InMemoryIconCacheProvider>();

services.AddCmsAspNetIdentity<ApplicationUser>();
services.AddMvc();
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System.IO;
using EPiServer.Framework.Internal;
using Geta.Optimizely.ContentTypeIcons.Infrastructure.Configuration;
using Microsoft.Extensions.Options;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Png;

namespace Geta.Optimizely.ContentTypeIcons.Caching
{
public class DiskIconCacheProvider : IIconCacheProvider
{
private readonly IPhysicalPathResolver _physicalPathResolver;
private readonly ContentTypeIconOptions _options;

public DiskIconCacheProvider(
IOptions<ContentTypeIconOptions> options,
IPhysicalPathResolver physicalPathResolver)
{
_physicalPathResolver = physicalPathResolver;
_options = options.Value;
}

public void Initialize()
{
var fullPath = _physicalPathResolver.Rebase(_options.CachePath);
if (!Directory.Exists(fullPath))
{
Directory.CreateDirectory(fullPath);
}
}

public bool TryGet(string key, out Image image)
{
var fullPath = GetFullPath(key);
if (File.Exists(fullPath))
{
image = Image.Load(fullPath);
return true;
}

image = null;
return false;
}

public void Set(string key, Image image)
{
var fullPath = GetFullPath(key);
using var fileStream = File.Create(fullPath);
image.Save(fileStream, new PngEncoder());
}

private string GetFullPath(string key)
{
return _physicalPathResolver.Rebase(_options.CachePath + key);
}
}
}
40 changes: 40 additions & 0 deletions src/Geta.Optimizely.ContentTypeIcons/Caching/IIconCacheProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using SixLabors.ImageSharp;

namespace Geta.Optimizely.ContentTypeIcons.Caching
{
public interface IIconCacheProvider
{
/// <summary>
/// Called once at application startup to allow the provider to perform any required initialization.
/// </summary>
void Initialize();

/// <summary>
/// Tries to retrieve a cached icon image by key.
/// </summary>
/// <param name="key">The cache key for the icon image.</param>
/// <param name="image">
/// When this method returns <c>true</c>, contains a newly created <see cref="Image"/> instance
/// representing the cached icon. Callers own the returned image and are responsible for disposing it.
/// Implementations must not return a shared or reused <see cref="Image"/> instance that may be
/// disposed or mutated elsewhere.
/// </param>
/// <returns>
/// <c>true</c> if an icon image was found for the specified key; otherwise, <c>false</c>. When this
/// method returns <c>false</c>, <paramref name="image"/> must be set to <c>null</c>.
/// </returns>
bool TryGet(string key, out Image image);

/// <summary>
/// Stores an icon image in the cache under the given key.
/// </summary>
Comment thread
degborta marked this conversation as resolved.
/// <param name="key">The cache key under which to store the icon image.</param>
/// <param name="image">
/// The icon image to cache. The caller will dispose this <see cref="Image"/> instance immediately
/// after <see cref="Set"/> returns, so implementations must not store or otherwise retain a reference
/// to this instance. Providers that need to keep the image must clone, serialize, or otherwise create
/// their own representation for long-term storage.
/// </param>
void Set(string key, Image image);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System.IO;
using Geta.Optimizely.ContentTypeIcons.Infrastructure.Configuration;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Png;

namespace Geta.Optimizely.ContentTypeIcons.Caching
{
public class InMemoryIconCacheProvider : IIconCacheProvider
{
private const string CacheKeyPrefix = "geta.contenttypeicons.image.";

private readonly IMemoryCache _cache;
private readonly ContentTypeIconOptions _options;

public InMemoryIconCacheProvider(IMemoryCache cache, IOptions<ContentTypeIconOptions> options)
{
_cache = cache;
_options = options.Value;
}

public void Initialize() { }

public bool TryGet(string key, out Image image)
{
if (_cache.TryGetValue(CacheKeyPrefix + key, out byte[] bytes))
{
image = Image.Load(bytes);
return true;
}

image = null;
return false;
}

public void Set(string key, Image image)
{
using var ms = new MemoryStream();
image.Save(ms, new PngEncoder());
var entryOptions = new MemoryCacheEntryOptions
{
SlidingExpiration = _options.InMemoryCacheSlidingExpiration
};
_cache.Set(CacheKeyPrefix + key, ms.ToArray(), entryOptions);
}
Comment thread
degborta marked this conversation as resolved.
}
}
21 changes: 9 additions & 12 deletions src/Geta.Optimizely.ContentTypeIcons/ContentTypeIconService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Numerics;
using EPiServer.Framework.Internal;
using EPiServer.Shell;
using Geta.Optimizely.ContentTypeIcons.Caching;
using Geta.Optimizely.ContentTypeIcons.Infrastructure.Configuration;
using Geta.Optimizely.ContentTypeIcons.Settings;
using Microsoft.Extensions.Caching.Memory;
Expand All @@ -24,17 +25,20 @@ public class ContentTypeIconService : IContentTypeIconService
private readonly IPhysicalPathResolver _physicalPathResolver;
private readonly IMemoryCache _cache;
private readonly ContentTypeIconOptions _configuration;
private readonly IIconCacheProvider _iconCacheProvider;

public ContentTypeIconService(
IOptions<ContentTypeIconOptions> options,
IFileProvider fileProvider,
IPhysicalPathResolver physicalPathResolver,
IMemoryCache cache)
IMemoryCache cache,
IIconCacheProvider iconCacheProvider)
{
_fileProvider = fileProvider;
_physicalPathResolver = physicalPathResolver;
_cache = cache;
_configuration = options.Value;
_iconCacheProvider = iconCacheProvider;
}

/// <summary>
Expand All @@ -44,19 +48,17 @@ public ContentTypeIconService(
/// <returns></returns>
public virtual Image LoadIconImage(ContentTypeIconSettings settings)
{
var fileName = settings.GetFileName(".png");
var cachePath = GetFileFullPath(fileName);
var key = settings.GetFileName(".png");

if (File.Exists(cachePath))
if (_iconCacheProvider.TryGet(key, out var cachedImage))
{
return Image.Load(cachePath);
return cachedImage;
}

using var stream = GenerateImage(settings);
using var fileStream = File.Create(cachePath);
using var img = Image.Load(stream);

img.Save(fileStream, new PngEncoder());
_iconCacheProvider.Set(key, img);

return img.Clone(_ => { });
}
Expand Down Expand Up @@ -171,10 +173,5 @@ protected virtual FontFamily LoadFontFamilyFromDisk(string fileName)
return fontCollection.Families.First();
}

protected virtual string GetFileFullPath(string fileName)
{
var rootPath = _configuration.CachePath;
return _physicalPathResolver.Rebase(rootPath + fileName);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace Geta.Optimizely.ContentTypeIcons.Infrastructure.Configuration
using System;

namespace Geta.Optimizely.ContentTypeIcons.Infrastructure.Configuration
{
public class ContentTypeIconOptions
{
Expand All @@ -21,5 +23,8 @@ public class ContentTypeIconOptions
public int FontSize { get; set; } = DefaultFontSize;

public bool EnableTreeIcons { get; set; }

public static readonly TimeSpan DefaultInMemoryCacheSlidingExpiration = TimeSpan.FromHours(24);
public TimeSpan InMemoryCacheSlidingExpiration { get; set; } = DefaultInMemoryCacheSlidingExpiration;
}
}
Loading
Loading