mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge pull request #2501 from Flow-Launcher/new-image-cache
Use 3rd party for image cache
This commit is contained in:
commit
335d084adc
4 changed files with 39 additions and 52 deletions
1
.github/actions/spelling/expect.txt
vendored
1
.github/actions/spelling/expect.txt
vendored
|
|
@ -106,3 +106,4 @@ alreadyexists
|
||||||
JsonRPC
|
JsonRPC
|
||||||
JsonRPCV2
|
JsonRPCV2
|
||||||
Softpedia
|
Softpedia
|
||||||
|
img
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue