Merge pull request #2501 from Flow-Launcher/new-image-cache

Use 3rd party for image cache
This commit is contained in:
Jeremy Wu 2024-03-28 14:14:18 +11:00 committed by GitHub
commit 335d084adc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 39 additions and 52 deletions

View file

@ -106,3 +106,4 @@ alreadyexists
JsonRPC JsonRPC
JsonRPCV2 JsonRPCV2
Softpedia Softpedia
img

View file

@ -49,6 +49,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Ben.Demystifier" Version="0.4.1" /> <PackageReference Include="Ben.Demystifier" Version="0.4.1" />
<PackageReference Include="FastCache.Cached" Version="1.8.2" />
<PackageReference Include="Fody" Version="6.5.5"> <PackageReference Include="Fody" Version="6.5.5">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View file

@ -4,13 +4,13 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Windows.Media; using System.Windows.Media;
using FastCache;
using FastCache.Services;
namespace Flow.Launcher.Infrastructure.Image namespace Flow.Launcher.Infrastructure.Image
{ {
[Serializable]
public class ImageUsage public class ImageUsage
{ {
public int usage; public int usage;
public ImageSource imageSource; public ImageSource imageSource;
@ -23,16 +23,13 @@ namespace Flow.Launcher.Infrastructure.Image
public class ImageCache public class ImageCache
{ {
private const int MaxCached = 50; private const int MaxCached = 150;
public ConcurrentDictionary<(string, bool), ImageUsage> Data { get; } = new();
private const int permissibleFactor = 2;
private SemaphoreSlim semaphore = new(1, 1);
public void Initialize(Dictionary<(string, bool), int> usage) public void Initialize(Dictionary<(string, bool), int> usage)
{ {
foreach (var key in usage.Keys) foreach (var key in usage.Keys)
{ {
Data[key] = new ImageUsage(usage[key], null); Cached<ImageUsage>.Save(key, new ImageUsage(usage[key], null), TimeSpan.MaxValue, MaxCached);
} }
} }
@ -40,70 +37,48 @@ namespace Flow.Launcher.Infrastructure.Image
{ {
get get
{ {
if (!Data.TryGetValue((path, isFullImage), out var value)) if (!Cached<ImageUsage>.TryGet((path, isFullImage), out var value))
{ {
return null; return null;
} }
value.usage++;
return value.imageSource;
value.Value.usage++;
return value.Value.imageSource;
} }
set set
{ {
Data.AddOrUpdate( if (Cached<ImageUsage>.TryGet((path, isFullImage), out var cached))
(path, isFullImage),
new ImageUsage(0, value),
(k, v) =>
{
v.imageSource = value;
v.usage++;
return v;
}
);
SliceExtra();
async void SliceExtra()
{ {
// To prevent the dictionary from drastically increasing in size by caching images, the dictionary size is not allowed to grow more than the permissibleFactor * maxCached size cached.Value.imageSource = value;
// This is done so that we don't constantly perform this resizing operation and also maintain the image cache size at the same time cached.Value.usage++;
if (Data.Count > permissibleFactor * MaxCached)
{
await semaphore.WaitAsync().ConfigureAwait(false);
// To delete the images from the data dictionary based on the resizing of the Usage Dictionary
// Double Check to avoid concurrent remove
if (Data.Count > permissibleFactor * MaxCached)
foreach (var key in Data.OrderBy(x => x.Value.usage).Take(Data.Count - MaxCached).Select(x => x.Key))
Data.TryRemove(key, out _);
semaphore.Release();
}
} }
Cached<ImageUsage>.Save((path, isFullImage), new ImageUsage(0, value), TimeSpan.MaxValue,
MaxCached);
} }
} }
public bool ContainsKey(string key, bool isFullImage) public bool ContainsKey(string key, bool isFullImage)
{ {
return key is not null && Data.ContainsKey((key, isFullImage)) && Data[(key, isFullImage)].imageSource != null; return Cached<ImageUsage>.TryGet((key, isFullImage), out _);
} }
public bool TryGetValue(string key, bool isFullImage, out ImageSource image) public bool TryGetValue(string key, bool isFullImage, out ImageSource image)
{ {
if (key is not null) if (Cached<ImageUsage>.TryGet((key, isFullImage), out var value))
{ {
bool hasKey = Data.TryGetValue((key, isFullImage), out var imageUsage); image = value.Value.imageSource;
image = hasKey ? imageUsage.imageSource : null; value.Value.usage++;
return hasKey; return image != null;
}
else
{
image = null;
return false;
} }
image = null;
return false;
} }
public int CacheSize() public int CacheSize()
{ {
return Data.Count; return CacheManager.TotalCount<(string, bool), ImageUsage>();
} }
/// <summary> /// <summary>
@ -111,7 +86,14 @@ namespace Flow.Launcher.Infrastructure.Image
/// </summary> /// </summary>
public int UniqueImagesInCache() public int UniqueImagesInCache()
{ {
return Data.Values.Select(x => x.imageSource).Distinct().Count(); return CacheManager.EnumerateEntries<(string, bool), ImageUsage>().Select(x => x.Value.imageSource)
.Distinct()
.Count();
}
public IEnumerable<Cached<(string, bool), ImageUsage>> EnumerateEntries()
{
return CacheManager.EnumerateEntries<(string, bool), ImageUsage>();
} }
} }
} }

View file

@ -49,7 +49,7 @@ namespace Flow.Launcher.Infrastructure.Image
{ {
await Stopwatch.NormalAsync("|ImageLoader.Initialize|Preload images cost", async () => await Stopwatch.NormalAsync("|ImageLoader.Initialize|Preload images cost", async () =>
{ {
foreach (var ((path, isFullImage), _) in ImageCache.Data) foreach (var ((path, isFullImage), _) in usage)
{ {
await LoadAsync(path, isFullImage); await LoadAsync(path, isFullImage);
} }
@ -65,7 +65,7 @@ namespace Flow.Launcher.Infrastructure.Image
try try
{ {
_storage.SaveAsync(ImageCache.Data await _storage.SaveAsync(ImageCache.EnumerateEntries()
.ToDictionary( .ToDictionary(
x => x.Key, x => x.Key,
x => x.Value.usage)); x => x.Value.usage));
@ -125,9 +125,12 @@ namespace Flow.Launcher.Infrastructure.Image
return new ImageResult(MissingImage, ImageType.Error); return new ImageResult(MissingImage, ImageType.Error);
} }
if (ImageCache.ContainsKey(path, loadFullImage)) // extra scope for use of same variable name
{ {
return new ImageResult(ImageCache[path, loadFullImage], ImageType.Cache); if (ImageCache.TryGetValue(path, loadFullImage, out var imageSource))
{
return new ImageResult(imageSource, ImageType.Cache);
}
} }
if (Uri.TryCreate(path, UriKind.RelativeOrAbsolute, out var uriResult) if (Uri.TryCreate(path, UriKind.RelativeOrAbsolute, out var uriResult)