DiskManager.cs 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Threading;
  5. using Best.HTTP.Shared.Extensions;
  6. using Best.HTTP.Shared.PlatformSupport.Memory;
  7. using Best.HTTP.Shared.PlatformSupport.Threading;
  8. using Best.HTTP.Shared.Streams;
  9. namespace Best.HTTP.Shared.Databases
  10. {
  11. public sealed class DiskManagerOptions
  12. {
  13. // avg. size of certificates is 1390 (calculated from 2621 intermediate certificate)
  14. public int MaxCacheSizeInBytes = 5 * 1024;
  15. public string HashDigest = "SHA256";
  16. }
  17. public interface IDiskContentParser<T>
  18. {
  19. T Parse(Stream stream, int length);
  20. void Encode(Stream stream, T content);
  21. }
  22. public sealed class DiskManager<T> : IDisposable
  23. {
  24. // TODO: store usage date/count and delete the oldest/least used?
  25. struct CachePointer<CacheType>
  26. {
  27. public static readonly CachePointer<CacheType> Empty = new CachePointer<CacheType> { Position = -1, Length = -1, Content = default(CacheType) };
  28. public int Position;
  29. public int Length;
  30. public CacheType Content;
  31. public override string ToString()
  32. {
  33. return $"[CachePointer<{this.Content.GetType().Name}>({Position}, {Length}, {Content})]";
  34. }
  35. }
  36. /// <summary>
  37. /// Sum size of the cached contents
  38. /// </summary>
  39. public int CacheSize { get; private set; }
  40. private Stream stream;
  41. private List<CachePointer<T>> cache = new List<CachePointer<T>>();
  42. private IDiskContentParser<T> diskContentParser;
  43. private DiskManagerOptions options;
  44. private FreeListManager freeListManager;
  45. private ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
  46. public DiskManager(Stream stream, Stream freeListStream, IDiskContentParser<T> contentParser, DiskManagerOptions options)
  47. {
  48. if (!stream.CanSeek)
  49. throw new ArgumentException("DiskManager - stream can't seek!");
  50. this.stream = stream;
  51. this.freeListManager = new FreeListManager(freeListStream);
  52. this.diskContentParser = contentParser;
  53. this.options = options;
  54. }
  55. public (int, int) Append(T content)
  56. {
  57. using (new WriteLock(this.rwLock))
  58. {
  59. int pos = -1;
  60. int length = -1;
  61. using (var buffer = new BufferPoolMemoryStream())
  62. {
  63. diskContentParser.Encode(buffer, content);
  64. length = (int)buffer.Length;
  65. var idx = this.freeListManager.FindFreeIndex(length);
  66. if (idx >= 0)
  67. pos = this.freeListManager.Occupy(idx, length);
  68. else
  69. pos = (int)this.stream.Length;
  70. this.stream.Seek(pos, SeekOrigin.Begin);
  71. buffer.Seek(0, SeekOrigin.Begin);
  72. buffer.CopyTo(this.stream);
  73. }
  74. this.stream.Flush();
  75. return (pos, length);
  76. }
  77. }
  78. public void SaveChanged(Metadata metadata, T content)
  79. {
  80. using var _ = new WriteLock(this.rwLock);
  81. int pos = -1;
  82. int length = -1;
  83. using (var buffer = new BufferPoolMemoryStream())
  84. {
  85. diskContentParser.Encode(buffer, content);
  86. length = (int)buffer.Length;
  87. this.freeListManager.Add(metadata.FilePosition, metadata.Length);
  88. var idx = this.freeListManager.FindFreeIndex(length);
  89. if (idx >= 0)
  90. pos = this.freeListManager.Occupy(idx, length);
  91. else
  92. pos = (int)this.stream.Length;
  93. this.stream.Seek(pos, SeekOrigin.Begin);
  94. buffer.Seek(0, SeekOrigin.Begin);
  95. buffer.CopyTo(this.stream);
  96. }
  97. this.stream.Flush();
  98. metadata.FilePosition = pos;
  99. metadata.Length = length;
  100. }
  101. public void Delete(Metadata metadata)
  102. {
  103. using (new WriteLock(this.rwLock))
  104. {
  105. this.freeListManager.Add(metadata.FilePosition, metadata.Length);
  106. this.stream.Seek(metadata.FilePosition, SeekOrigin.Begin);
  107. var buffer = BufferPool.Get(BufferPool.MinBufferSize, true);
  108. Array.Clear(buffer, 0, (int)BufferPool.MinBufferSize);
  109. int length = metadata.Length;
  110. int iterationCount = length / (int)BufferPool.MinBufferSize;
  111. for (int i = 0; i < iterationCount; ++i)
  112. {
  113. this.stream.Write(buffer, 0, (int)BufferPool.MinBufferSize);
  114. length -= (int)BufferPool.MinBufferSize;
  115. }
  116. this.stream.Write(buffer, 0, length);
  117. this.stream.Flush();
  118. BufferPool.Release(buffer);
  119. }
  120. }
  121. public T Load(Metadata metadata)
  122. {
  123. using (new WriteLock(this.rwLock))
  124. {
  125. T parsedContent = default(T);
  126. var cachePointer = GetCached(metadata.FilePosition);
  127. if (cachePointer.Position != -1 && cachePointer.Content != null)
  128. return cachePointer.Content;
  129. this.stream.Seek(metadata.FilePosition, SeekOrigin.Begin);
  130. parsedContent = diskContentParser.Parse(this.stream, metadata.Length);
  131. AddToCache(parsedContent, metadata.FilePosition, metadata.Length);
  132. return parsedContent;
  133. }
  134. }
  135. public List<KeyValuePair<Meta, T>> LoadAll<Meta>(List<Meta> metadatas) where Meta : Metadata
  136. {
  137. using (new WriteLock(this.rwLock))
  138. {
  139. if (metadatas == null || metadatas.Count == 0)
  140. return null;
  141. metadatas.Sort((m1, m2) => m1.FilePosition.CompareTo(m2.FilePosition));
  142. List<KeyValuePair<Meta, T>> result = new List<KeyValuePair<Meta, T>>(metadatas.Count);
  143. for (int i = 0; i < metadatas.Count; ++i)
  144. {
  145. var metadata = metadatas[i];
  146. result.Add(new KeyValuePair<Meta, T>(metadata, Load(metadata)));
  147. }
  148. return result;
  149. }
  150. }
  151. public void Clear()
  152. {
  153. using (new WriteLock(this.rwLock))
  154. {
  155. this.freeListManager.Clear();
  156. this.stream.SetLength(0);
  157. this.stream.Flush();
  158. this.cache.Clear();
  159. this.CacheSize = 0;
  160. }
  161. }
  162. private CachePointer<T> GetCached(int position)
  163. {
  164. for (int i = 0; i < this.cache.Count; ++i)
  165. {
  166. var cache = this.cache[i];
  167. if (cache.Position == position)
  168. return cache;
  169. }
  170. return CachePointer<T>.Empty;
  171. }
  172. private void AddToCache(T parsedContent, int pos, int length)
  173. {
  174. if (this.options.MaxCacheSizeInBytes >= length)
  175. {
  176. this.cache.Insert(0, new CachePointer<T>
  177. {
  178. Position = pos,
  179. Length = length,
  180. Content = parsedContent
  181. });
  182. this.CacheSize += length;
  183. }
  184. while (this.CacheSize > this.options.MaxCacheSizeInBytes && this.cache.Count > 0)
  185. {
  186. var removingCache = this.cache[this.cache.Count - 1];
  187. this.cache.RemoveAt(this.cache.Count - 1);
  188. this.CacheSize -= removingCache.Length;
  189. }
  190. }
  191. public BufferSegment CalculateHash()
  192. {
  193. using (new WriteLock(this.rwLock))
  194. {
  195. this.stream.Seek(0, SeekOrigin.Begin);
  196. #if UNITY_WEBGL && !UNITY_EDITOR
  197. var hash = System.Security.Cryptography.HashAlgorithm.Create(this.options.HashDigest);
  198. var result = hash.ComputeHash(this.stream);
  199. return new BufferSegment(result, 0, result.Length);
  200. #elif !BESTHTTP_DISABLE_ALTERNATE_SSL
  201. var digest = Best.HTTP.SecureProtocol.Org.BouncyCastle.Security.DigestUtilities.GetDigest(this.options.HashDigest);
  202. byte[] buffer = BufferPool.Get(4 * 1024, true);
  203. int readCount = 0;
  204. while ((readCount = this.stream.Read(buffer, 0, buffer.Length)) > 0)
  205. {
  206. digest.BlockUpdate(buffer, 0, readCount);
  207. }
  208. BufferPool.Release(buffer);
  209. byte[] result = BufferPool.Get(digest.GetDigestSize(), true);
  210. int length = digest.DoFinal(result, 0);
  211. return new BufferSegment(result, 0, length);
  212. #else
  213. throw new NotImplementedException(nameof(CalculateHash));
  214. #endif
  215. }
  216. }
  217. public void Save()
  218. {
  219. this.stream.Flush();
  220. this.freeListManager.Save();
  221. }
  222. public void Dispose()
  223. {
  224. this.freeListManager.Dispose();
  225. this.stream.Flush();
  226. this.stream.Close();
  227. this.stream = null;
  228. this.rwLock.Dispose();
  229. GC.SuppressFinalize(this);
  230. }
  231. }
  232. }