ImageCache.cs
1 // Copyright (c) Microsoft Corporation 2 // The Microsoft Corporation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System; 6 using System.Collections.Concurrent; 7 using System.Collections.Generic; 8 using System.Linq; 9 using System.Windows.Media; 10 11 using Wox.Infrastructure.Storage; 12 using Wox.Plugin; 13 using Wox.Plugin.Logger; 14 15 namespace Wox.Infrastructure.Image 16 { 17 [Serializable] 18 public class ImageCache 19 { 20 private const int MaxCached = 50; 21 private const int PermissibleFactor = 2; 22 23 private readonly ConcurrentDictionary<string, ImageSource> _data = new ConcurrentDictionary<string, ImageSource>(); 24 private readonly WoxJsonStorage<ConcurrentDictionary<string, int>> _storage; 25 26 public ConcurrentDictionary<string, int> Usage { get; private set; } = new ConcurrentDictionary<string, int>(); 27 28 public ImageCache() 29 { 30 _storage = new WoxJsonStorage<ConcurrentDictionary<string, int>>("ImageCache"); 31 Usage = _storage.Load(); 32 } 33 34 public ImageSource this[string path] 35 { 36 get 37 { 38 Usage.AddOrUpdate(path, 1, (k, v) => v + 1); 39 _data.TryGetValue(path, out ImageSource i); 40 41 if (i == null) 42 { 43 Log.Warn($"ImageSource is null for path: {path}", GetType()); 44 } 45 46 return i; 47 } 48 49 set 50 { 51 _data[path] = value; 52 53 // 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 54 // This is done so that we don't constantly perform this resizing operation and also maintain the image cache size at the same time 55 if (_data.Count > PermissibleFactor * MaxCached) 56 { 57 // This function resizes the Usage dictionary, taking the top 'maxCached' number of items and filtering the image icons that are not accessed frequently. 58 Cleanup(); 59 60 // To delete the images from the data dictionary based on the resizing of the Usage Dictionary. 61 foreach (var key in _data.Keys) 62 { 63 // Using Ordinal since this is internal 64 if (!Usage.TryGetValue(key, out _) && !(key.Equals(Constant.ErrorIcon, StringComparison.Ordinal) || key.Equals(Constant.LightThemedErrorIcon, StringComparison.Ordinal))) 65 { 66 _data.TryRemove(key, out _); 67 } 68 } 69 } 70 } 71 } 72 73 public void Cleanup() 74 { 75 var images = Usage 76 .OrderByDescending(o => o.Value) 77 .Take(MaxCached) 78 .ToDictionary(i => i.Key, i => i.Value); 79 Usage = new ConcurrentDictionary<string, int>(images); 80 } 81 82 public bool ContainsKey(string key) 83 { 84 var contains = _data.ContainsKey(key); 85 return contains; 86 } 87 88 public int CacheSize() 89 { 90 return _data.Count; 91 } 92 93 /// <summary> 94 /// return the number of unique images in the cache (by reference not by checking images content) 95 /// </summary> 96 public int UniqueImagesInCache() 97 { 98 return _data.Values.Distinct().Count(); 99 } 100 101 public Dictionary<string, int> GetUsageAsDictionary() 102 { 103 return new Dictionary<string, int>(Usage); 104 } 105 106 public void Save() 107 { 108 _storage.Save(); 109 } 110 } 111 }