PreloaderService.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. using ImageMagick;
  2. using PicView.Avalonia.Models;
  3. using PicView.Core.ImageDecoding;
  4. using System.Collections.Concurrent;
  5. using System.Diagnostics;
  6. namespace PicView.Avalonia.Navigation;
  7. internal class PreLoader
  8. {
  9. private static bool _isRunning;
  10. internal class PreLoadValue
  11. {
  12. internal ImageModel? ImageModel { get; set; }
  13. internal bool IsLoading = true;
  14. internal PreLoadValue(ImageModel? imageModel)
  15. {
  16. ImageModel = imageModel;
  17. }
  18. }
  19. private readonly ConcurrentDictionary<int, PreLoadValue> _preLoadList = new();
  20. private const int PositiveIterations = 8;
  21. private const int NegativeIterations = 4;
  22. internal const int MaxCount = PositiveIterations + NegativeIterations + 2;
  23. #if DEBUG
  24. // ReSharper disable once ConvertToConstant.Local
  25. private static readonly bool ShowAddRemove = true;
  26. #endif
  27. internal async Task<bool> AddAsync(int index, List<string> list, ImageModel? imageModel = null)
  28. {
  29. if (list == null)
  30. {
  31. #if DEBUG
  32. Trace.WriteLine($"{nameof(PreLoader)}.{nameof(AddAsync)} list null \n{index}");
  33. #endif
  34. return false;
  35. }
  36. if (index < 0 || index >= list.Count)
  37. {
  38. #if DEBUG
  39. Trace.WriteLine($"{nameof(PreLoader)}.{nameof(AddAsync)} invalid index: \n{index}");
  40. #endif
  41. return false;
  42. }
  43. var preLoadValue = new PreLoadValue(imageModel);
  44. try
  45. {
  46. var add = _preLoadList.TryAdd(index, preLoadValue);
  47. if (add)
  48. {
  49. imageModel ??= new ImageModel();
  50. imageModel.FileInfo ??= new FileInfo(list[index]);
  51. preLoadValue.ImageModel = imageModel;
  52. if (imageModel.Image is null)
  53. {
  54. preLoadValue.IsLoading = true;
  55. await ImageModel.LoadImageAsync(imageModel).ConfigureAwait(false);
  56. }
  57. if (imageModel.EXIFOrientation is null || imageModel is { EXIFOrientation: EXIFHelper.EXIFOrientation.None, Image: not null })
  58. {
  59. using var magickImage = new MagickImage(imageModel.FileInfo);
  60. preLoadValue.ImageModel.EXIFOrientation = EXIFHelper.GetImageOrientation(magickImage);
  61. }
  62. else
  63. {
  64. preLoadValue.ImageModel.EXIFOrientation = EXIFHelper.EXIFOrientation.None;
  65. }
  66. #if DEBUG
  67. if (ShowAddRemove)
  68. Trace.WriteLine($"{imageModel.FileInfo.Name} added at {index}");
  69. #endif
  70. return true;
  71. }
  72. }
  73. catch (Exception ex)
  74. {
  75. #if DEBUG
  76. Trace.WriteLine($"{nameof(AddAsync)} exception: \n{ex}");
  77. #endif
  78. }
  79. finally
  80. {
  81. preLoadValue.IsLoading = false;
  82. }
  83. return false;
  84. }
  85. internal async Task<bool> RefreshFileInfo(int index, List<string> list)
  86. {
  87. if (list == null)
  88. {
  89. #if DEBUG
  90. Trace.WriteLine($"{nameof(PreLoader)}.{nameof(RefreshFileInfo)} list null \n{index}");
  91. #endif
  92. return false;
  93. }
  94. if (index < 0 || index >= list.Count)
  95. {
  96. #if DEBUG
  97. Trace.WriteLine($"{nameof(PreLoader)}.{nameof(RefreshFileInfo)} invalid index: \n{index}");
  98. #endif
  99. return false;
  100. }
  101. var removed = _preLoadList.TryRemove(index, out var preLoadValue);
  102. if (preLoadValue is not null)
  103. {
  104. preLoadValue.ImageModel.FileInfo = null;
  105. }
  106. await AddAsync(index, list, preLoadValue.ImageModel).ConfigureAwait(false);
  107. return removed;
  108. }
  109. /// <summary>
  110. /// Removes all keys from the cache.
  111. /// </summary>
  112. internal void Clear()
  113. {
  114. _preLoadList.Clear();
  115. }
  116. internal PreLoadValue? Get(int key, List<string> list)
  117. {
  118. if (list == null)
  119. {
  120. #if DEBUG
  121. Trace.WriteLine($"{nameof(PreLoader)}.{nameof(Get)} list null \n{key}");
  122. #endif
  123. return null;
  124. }
  125. if (key < 0 || key >= list.Count)
  126. {
  127. #if DEBUG
  128. Trace.WriteLine($"{nameof(PreLoader)}.{nameof(Get)} invalid key: \n{key}");
  129. #endif
  130. return null;
  131. }
  132. return !Contains(key, list) ? null : _preLoadList[key];
  133. }
  134. internal bool Contains(int key, List<string> list)
  135. {
  136. if (list == null)
  137. {
  138. #if DEBUG
  139. Trace.WriteLine($"{nameof(PreLoader)}.{nameof(Get)} list null \n{key}");
  140. #endif
  141. return false;
  142. }
  143. if (key < 0 || key >= list.Count)
  144. {
  145. #if DEBUG
  146. Trace.WriteLine($"{nameof(PreLoader)}.{nameof(Contains)} invalid key: \n{key}");
  147. #endif
  148. return false;
  149. }
  150. return !_preLoadList.IsEmpty && _preLoadList.ContainsKey(key);
  151. }
  152. internal bool Remove(int key, List<string> list)
  153. {
  154. if (list == null)
  155. {
  156. #if DEBUG
  157. Trace.WriteLine($"{nameof(PreLoader)}.{nameof(Get)} list null \n{key}");
  158. #endif
  159. return false;
  160. }
  161. if (key < 0 || key >= list.Count)
  162. {
  163. #if DEBUG
  164. Trace.WriteLine($"{nameof(PreLoader)}.{nameof(Remove)} invalid key: \n{key}");
  165. #endif
  166. return false;
  167. }
  168. if (!Contains(key, list))
  169. {
  170. return false;
  171. }
  172. try
  173. {
  174. _ = _preLoadList[key];
  175. var remove = _preLoadList.TryRemove(key, out _);
  176. #if DEBUG
  177. if (remove && ShowAddRemove)
  178. Trace.WriteLine($"{list[key]} removed at {list.IndexOf(list[key])}");
  179. #endif
  180. return remove;
  181. }
  182. catch (Exception e)
  183. {
  184. #if DEBUG
  185. Trace.WriteLine($"{nameof(Remove)} exception:\n{e.Message}");
  186. #endif
  187. return false;
  188. }
  189. }
  190. internal async Task PreLoadAsync(int currentIndex, int count, bool parallel, bool reverse, List<string> list)
  191. {
  192. if (list == null)
  193. {
  194. #if DEBUG
  195. Trace.WriteLine($"{nameof(PreLoader)}.{nameof(Get)} list null \n{currentIndex}");
  196. #endif
  197. return;
  198. }
  199. if (_isRunning)
  200. {
  201. return;
  202. }
  203. _isRunning = true;
  204. int nextStartingIndex, prevStartingIndex;
  205. if (reverse)
  206. {
  207. nextStartingIndex = (currentIndex - 1 + count) % count;
  208. prevStartingIndex = currentIndex + 1;
  209. }
  210. else
  211. {
  212. nextStartingIndex = (currentIndex + 1) % count;
  213. prevStartingIndex = currentIndex - 1;
  214. }
  215. var array = new int[MaxCount];
  216. #if DEBUG
  217. if (ShowAddRemove)
  218. Trace.WriteLine($"\nPreLoading started at {currentIndex}\n");
  219. #endif
  220. var options = parallel
  221. ? new ParallelOptions
  222. {
  223. MaxDegreeOfParallelism = Environment.ProcessorCount - 2 < 1 ? 1 : Environment.ProcessorCount - 2
  224. }
  225. : null;
  226. try
  227. {
  228. if (reverse)
  229. {
  230. await NegativeLoop(options);
  231. await PositiveLoop(options);
  232. }
  233. else
  234. {
  235. await PositiveLoop(options);
  236. await NegativeLoop(options);
  237. }
  238. }
  239. catch (Exception exception)
  240. {
  241. #if DEBUG
  242. Trace.WriteLine($"{nameof(PreLoadAsync)} exception:\n{exception.Message}");
  243. #endif
  244. }
  245. finally
  246. {
  247. _isRunning = false;
  248. }
  249. RemoveLoop();
  250. return;
  251. async Task PositiveLoop(ParallelOptions parallelOptions)
  252. {
  253. if (parallel)
  254. {
  255. await Parallel.ForAsync(0, PositiveIterations, parallelOptions, async (i, _) =>
  256. {
  257. if (list.Count == 0 || count != list.Count)
  258. {
  259. Clear();
  260. return;
  261. }
  262. var index = (nextStartingIndex + i) % list.Count;
  263. var isAdded = await AddAsync(index, list);
  264. if (isAdded)
  265. {
  266. array[i] = index;
  267. }
  268. });
  269. }
  270. else
  271. {
  272. for (var i = 0; i < PositiveIterations; i++)
  273. {
  274. if (list.Count == 0 || count != list.Count)
  275. {
  276. Clear();
  277. return;
  278. }
  279. var index = (nextStartingIndex + i) % list.Count;
  280. _ = AddAsync(index, list);
  281. array[i] = index;
  282. }
  283. }
  284. }
  285. async Task NegativeLoop(ParallelOptions parallelOptions)
  286. {
  287. if (parallel)
  288. {
  289. await Parallel.ForAsync(0, NegativeIterations, parallelOptions, async (i, _) =>
  290. {
  291. if (list.Count == 0 || count != list.Count)
  292. {
  293. Clear();
  294. return;
  295. }
  296. var index = (prevStartingIndex - i + list.Count) % list.Count;
  297. var isAdded = await AddAsync(index, list);
  298. if (isAdded)
  299. {
  300. array[i] = index;
  301. }
  302. });
  303. }
  304. else
  305. {
  306. for (var i = 0; i < NegativeIterations; i++)
  307. {
  308. if (list.Count == 0 || count != list.Count)
  309. {
  310. Clear();
  311. return;
  312. }
  313. var index = (prevStartingIndex - i + list.Count) % list.Count;
  314. _ = AddAsync(index, list);
  315. array[i] = index;
  316. }
  317. }
  318. }
  319. void RemoveLoop()
  320. {
  321. if (list.Count <= MaxCount + NegativeIterations || _preLoadList.Count <= MaxCount)
  322. {
  323. return;
  324. }
  325. var deleteCount = _preLoadList.Count - MaxCount < MaxCount ? MaxCount : _preLoadList.Count - MaxCount;
  326. for (var i = 0; i < deleteCount; i++)
  327. {
  328. var removeIndex = reverse ? _preLoadList.Keys.Max() : _preLoadList.Keys.Min();
  329. if (i >= array.Length)
  330. {
  331. return;
  332. }
  333. if (array[i] == removeIndex || removeIndex == currentIndex)
  334. {
  335. continue;
  336. }
  337. if (removeIndex > currentIndex + 2 || removeIndex < currentIndex - 2)
  338. {
  339. Remove(removeIndex, list);
  340. }
  341. }
  342. }
  343. }
  344. }