using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace TELTest { using System; using System.Collections.Generic; using System.IO; using System.Linq; internal class BasicKeyValue { public BasicKeyValue(string directoryName) { DirectoryName = directoryName; LoadAllKeys(); } public long MaximumMemoryPressure { get; set; } = 2L * 1024 * 1024 * 1024; // 2 GB public long CurrentMemoryPressure => m_CurrentMemoryPressure; public string DirectoryName { get; set; } = "Database"; public int LastTuplesOut { get; protected set; } private long m_CurrentMemoryPressure = 0; private readonly object _dmpLock = new object(); private readonly Dictionary m_LastAccess = new Dictionary(); private readonly HashSet m_AllKeys = new HashSet(StringComparer.OrdinalIgnoreCase); private readonly Dictionary m_ChunkMemoryPressure = new Dictionary(); private readonly Dictionary> m_Chunks = new Dictionary>(); public bool ContainsKey(string key) => m_AllKeys.Contains(key); public byte[] this[string key] { get { char q = char.ToUpper(key[0]); if (!m_AllKeys.Contains(key)) return null; if (!m_Chunks.ContainsKey(q) || !m_Chunks[q].ContainsKey(key)) { LoadAndFreeIfNeeded(q); } if (m_Chunks.ContainsKey(q) && m_Chunks[q].ContainsKey(key)) { m_LastAccess[q] = DateTime.Now; return m_Chunks[q][key]; } return null; } set => AddOrSet(key, value); } public void Remove(string key) { char c = char.ToUpper(key[0]); if (!m_AllKeys.Remove(key)) return; if (m_Chunks.ContainsKey(c)) { var oldValue = m_Chunks[c][key]; m_Chunks[c].Remove(key); m_ChunkMemoryPressure[c] -= oldValue.Length; m_CurrentMemoryPressure -= oldValue.Length; } else { LoadAndFreeIfNeeded(c); if (m_Chunks[c].ContainsKey(key)) { var loadedValue = m_Chunks[c][key]; if (m_Chunks[c].Remove(key)) { m_ChunkMemoryPressure[c] -= loadedValue.Length; m_CurrentMemoryPressure -= loadedValue.Length; } } } } public void SavePendingChanges() => DumpAll(); protected void AddOrSet(string key, byte[] value) { char q = char.ToUpper(key[0]); PrepAddChunk(q); if (m_Chunks[q].TryGetValue(key, out var oldValue)) { m_Chunks[q][key] = value; long delta = value.Length - oldValue.Length; m_ChunkMemoryPressure[q] += delta; m_CurrentMemoryPressure += delta; } else { m_AllKeys.Add(key); m_Chunks[q].Add(key, value); m_ChunkMemoryPressure[q] += value.Length; m_CurrentMemoryPressure += value.Length; } } protected void LoadAndFreeIfNeeded(char at) { while (m_CurrentMemoryPressure > MaximumMemoryPressure && m_LastAccess.Any()) { var oldest = m_LastAccess.OrderBy(x => x.Value).First(); UnloadChunk(oldest.Key); } LoadChunk(at); } protected void UnloadChunk(char chk) { chk = char.ToUpper(chk); if (!m_Chunks.ContainsKey(chk)) return; WriteChunk(chk); m_CurrentMemoryPressure -= m_ChunkMemoryPressure[chk]; m_ChunkMemoryPressure.Remove(chk); m_Chunks.Remove(chk); m_LastAccess.Remove(chk); } public void DumpAll() { LastTuplesOut = 0; lock (_dmpLock) { if (!Directory.Exists(DirectoryName)) Directory.CreateDirectory(DirectoryName); foreach (var item in m_Chunks.Keys.ToList()) { WriteChunk(item); } } } protected void WriteChunk(char c) { c = char.ToUpper(c); if (!m_Chunks.ContainsKey(c)) return; string fp = Path.Combine(DirectoryName, c + ".bin"); using (var str = File.Open(fp, FileMode.Create, FileAccess.Write)) using (var bw = new BinaryWriter(str, System.Text.Encoding.UTF8, false)) { foreach (var kv in m_Chunks[c]) { byte[] idBytes = System.Text.Encoding.UTF8.GetBytes(kv.Key); bw.Write(idBytes.Length); bw.Write(idBytes); byte[] valueBytes = kv.Value ?? new byte[0]; bw.Write(valueBytes.Length); bw.Write(valueBytes); LastTuplesOut++; } bw.Write(0); // terminator } } protected void LoadAllKeys() { if (!Directory.Exists(DirectoryName)) return; foreach (var chunk in Directory.GetFiles(DirectoryName, "*.bin")) { var cn = Path.GetFileNameWithoutExtension(chunk)[0]; m_LastAccess[cn] = DateTime.MinValue; using (var fs = File.OpenRead(chunk)) using (var br = new BinaryReader(fs, System.Text.Encoding.UTF8, true)) { while (true) { int idlen = br.ReadInt32(); if (idlen == 0) break; string id = System.Text.Encoding.UTF8.GetString(br.ReadBytes(idlen)); int ctlen = br.ReadInt32(); fs.Seek(ctlen, SeekOrigin.Current); m_AllKeys.Add(id); } } } } protected void PrepAddChunk(char chk) { chk = char.ToUpper(chk); if (!m_Chunks.ContainsKey(chk)) m_Chunks[chk] = new Dictionary(StringComparer.OrdinalIgnoreCase); if (!m_ChunkMemoryPressure.ContainsKey(chk)) m_ChunkMemoryPressure[chk] = 0L; m_LastAccess[chk] = DateTime.Now; } protected bool LoadChunk(char chk) { chk = char.ToUpper(chk); if (!Directory.Exists(DirectoryName)) return false; string path = Path.Combine(DirectoryName, chk + ".bin"); if (!File.Exists(path)) return false; if (m_Chunks.ContainsKey(chk)) return true; PrepAddChunk(chk); using (var str = File.OpenRead(path)) using (var br = new BinaryReader(str, System.Text.Encoding.UTF8, true)) { while (true) { int idl = br.ReadInt32(); if (idl == 0) break; string id = System.Text.Encoding.UTF8.GetString(br.ReadBytes(idl)); int ctlen = br.ReadInt32(); byte[] ctbytes = br.ReadBytes(ctlen); m_Chunks[chk][id] = ctbytes; m_AllKeys.Add(id); long mp = id.Length + ctbytes.Length; m_CurrentMemoryPressure += mp; m_ChunkMemoryPressure[chk] += mp; } } return true; } } }