-
Notifications
You must be signed in to change notification settings - Fork 2.4k
/
ImageCache.cs
137 lines (121 loc) · 4.51 KB
/
ImageCache.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
using JetBrains.Annotations;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Media;
using Wox.Helper;
using Wox.Infrastructure;
using Wox.Infrastructure.Logger;
namespace Wox.Image
{
class CacheEntry
{
internal string Key;
internal ImageSource Image;
internal DateTime ExpiredDate;
public CacheEntry(string key, ImageSource image)
{
Key = key;
Image = image;
ExpiredDate = DateTime.Now;
}
}
class ImageCache
{
private readonly TimeSpan _expiredTime = new TimeSpan(24, 0, 0);
private readonly TimeSpan _checkInterval = new TimeSpan(0, 1, 0);
private const int _cacheLimit = 500;
private readonly object _addLock = new object();
private readonly ConcurrentDictionary<String, CacheEntry> _cache;
private readonly SortedSet<CacheEntry> _cacheSorted;
private readonly Func<string, ImageSource> _imageFactory;
private readonly System.Threading.Timer timer;
private static readonly NLog.Logger Logger = LogManager.GetCurrentClassLogger();
public ImageCache(Func<string, ImageSource> imageFactory)
{
_imageFactory = imageFactory;
_cache = new ConcurrentDictionary<string, CacheEntry>();
_cacheSorted = new SortedSet<CacheEntry>(new CacheEntryComparer());
timer = new System.Threading.Timer(ExpirationCheck, null, _checkInterval, _checkInterval);
}
private void ExpirationCheck(object state)
{
DateTime now = DateTime.Now;
Logger.WoxDebug($"ExpirationCheck start {now}");
List<KeyValuePair<string, CacheEntry>> pairs = _cache.Where(pair => now > pair.Value.ExpiredDate).ToList();
foreach (KeyValuePair<string, CacheEntry> pair in pairs)
{
bool success = _cache.TryRemove(pair.Key, out CacheEntry entry);
Logger.WoxDebug($"remove expired: <{success}> entry: <{pair.Key}>");
}
}
/// <summary>
/// Not thread safe, should be only called from ui thread
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public ImageSource GetOrAdd([NotNull] string key)
{
key.RequireNonNull();
CacheEntry entry;
bool getResult = _cache.TryGetValue(key, out entry);
if (!getResult)
{
entry = Add(key);
}
entry.ExpiredDate = DateTime.Now + _expiredTime;
return entry.Image;
}
public ImageSource GetOrAdd([NotNull] string key, ImageSource defaultImage, Action<ImageSource> updateImageCallback)
{
key.RequireNonNull();
CacheEntry getEntry;
bool getResult = _cache.TryGetValue(key, out getEntry);
if (!getResult)
{
var t = Task.Run(() =>
{
CacheEntry addEntry = Add(key);
addEntry.ExpiredDate = DateTime.Now + _expiredTime;
updateImageCallback(addEntry.Image);
}).ContinueWith(ErrorReporting.UnhandledExceptionHandleTask, TaskContinuationOptions.OnlyOnFaulted);
return defaultImage;
}
else
{
getEntry.ExpiredDate = DateTime.Now + _expiredTime;
return getEntry.Image;
}
}
private CacheEntry Add(string key)
{
lock (_addLock)
{
CacheEntry entry;
ImageSource image = _imageFactory(key);
entry = new CacheEntry(key, image);
_cache[key] = entry;
_cacheSorted.Add(entry);
int currentCount = _cache.Count;
if (currentCount > _cacheLimit)
{
CacheEntry min = _cacheSorted.Min;
_cacheSorted.Remove(min);
bool removeResult = _cache.TryRemove(min.Key, out _);
Logger.WoxDebug($"remove exceed: <{removeResult}> entry: <{min.Key}>");
}
return entry;
}
}
}
internal class CacheEntryComparer : IComparer<CacheEntry>
{
public int Compare(CacheEntry x, CacheEntry y)
{
return x.ExpiredDate.CompareTo(y.ExpiredDate);
}
}
}