using SQLite; using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Threading; namespace ZeroLevel.SqLite { public sealed class DuplicateRecord { [PrimaryKey, AutoIncrement] public long Id { get; set; } [Indexed] public string Hash { get; set; } public long Timestamp { get; set; } public byte[] Data { get; set; } } /// /// Хранит данные указанное число дней, и позволяет выполнить проверку наличия данных, для отбрасывания дубликатов /// public sealed class SqLiteDupStorage : BaseSqLiteDB { #region Fields private readonly long _removeOldRecordsTaskKey; private readonly int _countDays; private readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim(); #endregion Fields #region Private members private void RemoveOldRecordsTask(long key) { _rwLock.EnterWriteLock(); try { Delete(r => r.Timestamp < DateTime.Now.AddDays(-_countDays).Ticks); } catch (Exception ex) { Log.Error(ex, "[SQLiteDupStorage] Fault remove old records from db"); } finally { _rwLock.ExitWriteLock(); } } #endregion Private members #region Ctor public SqLiteDupStorage(string database_file_path, int period) : base(database_file_path) { CreateTable(); _countDays = period > 0 ? period : 1; _removeOldRecordsTaskKey = Sheduller.RemindEvery(TimeSpan.FromMinutes(1), RemoveOldRecordsTask); } #endregion Ctor #region API /// /// true в случае обнаружения дубликата /// public bool TestAndInsert(byte[] body) { var hash = GenerateSHA256String(body); var timestamp = DateTime.Now.Ticks; _rwLock.EnterReadLock(); var exists = new List(); try { foreach (var record in SelectBy(r => r.Hash == hash)) { exists.Add(record.Data); } } catch (Exception ex) { Log.Error(ex, $"[SQLiteDupStorage] Fault search existing records by hash ({hash})"); // no return! } finally { _rwLock.ExitReadLock(); } if (exists.Any()) { foreach (var candidate in exists) { if (ArrayExtensions.UnsafeEquals(candidate, body)) return true; } } _rwLock.EnterWriteLock(); try { Append(new DuplicateRecord { Data = body, Hash = hash, Timestamp = timestamp }); } catch (Exception ex) { Log.Error(ex, $"[SQLiteDupStorage] Fault insert record in duplications storage. Hash '{hash}'. Timestamp '{timestamp}'."); } finally { _rwLock.ExitWriteLock(); } return false; } #endregion API #region IDisposable protected override void DisposeStorageData() { Sheduller.Remove(_removeOldRecordsTaskKey); } #endregion IDisposable #region Helpers private static string GenerateSHA256String(byte[] bytes) { using (SHA256 sha256 = SHA256Managed.Create()) { byte[] hash = sha256.ComputeHash(bytes); return ByteArrayToString(hash); } } private static string ByteArrayToString(byte[] ba) { StringBuilder hex = new StringBuilder(ba.Length * 2); foreach (byte b in ba) hex.AppendFormat("{0:x2}", b); return hex.ToString(); } #endregion Helpers } }