using System.Collections.Concurrent; #pragma warning disable IDE0130 // Namespace does not match folder structure namespace ArmaRAMDb #pragma warning restore IDE0130 // Namespace does not match folder structure { public class RAMDb(string ardbPath = null) : IDisposable { private readonly string _ardbPath = Path.Combine(Environment.CurrentDirectory, ardbPath ?? Main.DEFAULT_ARDB_PATH); public static readonly ConcurrentDictionary _keyValues = new(); public static readonly ConcurrentDictionary> _hashTables = new(); public static readonly ConcurrentDictionary> _lists = new(); public static bool AutoBackupEnabled { get; set; } = false; public static int BackupFrequencyMinutes { get; set; } = 60; public static int MaxBackupsToKeep { get; set; } = 10; private static Timer _backupTimer; public void ImportFromBinary() { try { if (File.Exists(_ardbPath)) { using var stream = new FileStream(_ardbPath, FileMode.Open); using var reader = new BinaryReader(stream); int version = reader.ReadInt32(); if (version != 1) { Main.Log($"Unsupported ARDB format version: {version}", "warning"); return; } _keyValues.Clear(); _hashTables.Clear(); _lists.Clear(); // Read KeyValues int keyValueCount = reader.ReadInt32(); for (int i = 0; i < keyValueCount; i++) { string key = reader.ReadString(); string value = reader.ReadString(); _keyValues.TryAdd(key, value); Main.Log($"Loaded key-value: {key} = {value[..Math.Min(50, value.Length)]}...", "debug"); } // Read HashTables int tableCount = reader.ReadInt32(); for (int i = 0; i < tableCount; i++) { string tableName = reader.ReadString(); Main.Log($"Loading table: {tableName}", "debug"); var concurrentDict = new ConcurrentDictionary(); int entryCount = reader.ReadInt32(); for (int j = 0; j < entryCount; j++) { string key = reader.ReadString(); string value = reader.ReadString(); concurrentDict.TryAdd(key, value); Main.Log($"Loaded entry: {key} = {value[..Math.Min(50, value.Length)]}...", "debug"); } _hashTables.TryAdd(tableName, concurrentDict); } // Read Lists int listCount = reader.ReadInt32(); for (int i = 0; i < listCount; i++) { string listName = reader.ReadString(); Main.Log($"Loading list: {listName}", "debug"); var items = new List(); int itemCount = reader.ReadInt32(); for (int j = 0; j < itemCount; j++) { string value = reader.ReadString(); items.Add(value); Main.Log($"Loaded item: {value[..Math.Min(50, value.Length)]}...", "debug"); } _lists.TryAdd(listName, items); } Main.Log("ARDB import complete", "debug"); } else { Main.Log($"ARDB file not found at path: {_ardbPath}. A new database will be created when data is saved.", "info"); } } catch (Exception ex) { Main.Log($"Error during ARDB import: {ex.Message}", "error"); } } public void ExportToBinary(bool createBackup = false) { try { Directory.CreateDirectory(Path.GetDirectoryName(_ardbPath)); using (var stream = new FileStream(_ardbPath, FileMode.Create)) using (var writer = new BinaryWriter(stream)) { writer.Write(1); Utils.WriteDataToBinaryWriter(writer); } if (createBackup) { string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); string backupDirectory = Path.Combine(Path.GetDirectoryName(_ardbPath), "backups"); string backupFileName = Path.GetFileNameWithoutExtension(_ardbPath) + "_" + timestamp + Path.GetExtension(_ardbPath); string backupPath = Path.Combine(backupDirectory, backupFileName); Directory.CreateDirectory(backupDirectory); using (var stream = new FileStream(backupPath, FileMode.Create)) using (var writer = new BinaryWriter(stream)) { Utils.WriteDataToBinaryWriter(writer); } Main.Log($"Created backup at: {backupPath}", "debug"); } Main.Log("ARDB export complete", "debug"); } catch (Exception ex) { Main.Log($"Error during ARDB export: {ex.Message}", "error"); } } public List ListBackups() { string backupDirectory = Path.Combine(Path.GetDirectoryName(_ardbPath), "backups"); List backups = []; if (Directory.Exists(backupDirectory)) { backups = [.. Directory.GetFiles(backupDirectory, "*.ardb").OrderByDescending(file => file)]; } return backups; } public static bool RestoreFromBackup(string backupPath) { if (File.Exists(backupPath)) { try { using var stream = new FileStream(backupPath, FileMode.Open); using var reader = new BinaryReader(stream); int version = reader.ReadInt32(); if (version != 1) { Main.Log($"Unsupported ARDB format version in backup: {version}", "warning"); return false; } _keyValues.Clear(); _hashTables.Clear(); _lists.Clear(); Utils.ReadDataFromBinaryReader(reader); Main.Log($"Restored from backup: {backupPath}", "info"); return true; } catch (Exception ex) { Main.Log($"Failed to restore from backup: {ex.Message}", "error"); } } return false; } public static void InitializeAutoBackup() { if (AutoBackupEnabled) { _backupTimer?.Dispose(); _backupTimer = new Timer(BackupTimerCallback, null, TimeSpan.FromMinutes(BackupFrequencyMinutes), TimeSpan.FromMinutes(BackupFrequencyMinutes)); Main.Log($"Automatic backup initialized (every {BackupFrequencyMinutes} minutes)", "info"); } } private static void BackupTimerCallback(object state) { try { Main.db.ExportToBinary(true); ManageBackupRotation(); Main.Log($"Automatic backup created at {DateTime.Now}", "info"); } catch (Exception ex) { Main.Log($"Automatic backup failed: {ex.Message}", "error"); } } private static void ManageBackupRotation() { try { var backups = Main.db.ListBackups(); if (backups.Count > MaxBackupsToKeep) { for (int i = MaxBackupsToKeep; i < backups.Count; i++) { File.Delete(backups[i]); Main.Log($"Deleted old backup: {backups[i]}", "info"); } } } catch (Exception ex) { Main.Log($"Backup rotation failed: {ex.Message}", "error"); } } public void Dispose() { try { ExportToBinary(true); _backupTimer?.Dispose(); Main.Log("RAMDb disposed with final save and backup", "action"); } catch (Exception ex) { Main.Log($"Error during final save on disposal: {ex.Message}", "error"); } GC.SuppressFinalize(this); } } }