Update 03212025@1305
This commit is contained in:
parent
3f44f89099
commit
fac86fcda7
@ -1 +1 @@
|
|||||||
HEMTT_RFS.join("extension").join("bin").join("Release").join("net8.0").join("win-x64").join("publish").join("ArmaRAMDb_x64.so").copy(HEMTT_RFS.join("ArmaRAMDb_x64.so"));
|
HEMTT_RFS.join("extension").join("bin").join("Release").join("net8.0").join("linux-x64").join("publish").join("ArmaRAMDb_x64.so").copy(HEMTT_RFS.join("ArmaRAMDb_x64.so"));
|
Binary file not shown.
BIN
ArmaRAMDb_x64.so
BIN
ArmaRAMDb_x64.so
Binary file not shown.
@ -16,16 +16,25 @@
|
|||||||
* Save to disc.
|
* Save to disc.
|
||||||
*
|
*
|
||||||
* Arguments:
|
* Arguments:
|
||||||
* N/A
|
* 0: Type of save <STRING> (default: "xml")
|
||||||
|
* 1: Create backup <BOOL> (default: false)
|
||||||
*
|
*
|
||||||
* Return Value:
|
* Return Value:
|
||||||
* N/A
|
* N/A
|
||||||
*
|
*
|
||||||
* Examples:
|
* Examples:
|
||||||
* [] call ramdb_db_fnc_save (Server or Singleplayer Only)
|
* [] call ramdb_db_fnc_save (Server or Singleplayer Only)
|
||||||
|
* ["rdb", true] call ramdb_db_fnc_save (Server or Singleplayer Only)
|
||||||
* [] remoteExecCall ["ramdb_db_fnc_save", 2, false] (Multiplayer Only)
|
* [] remoteExecCall ["ramdb_db_fnc_save", 2, false] (Multiplayer Only)
|
||||||
|
* ["rdb", true] remoteExecCall ["ramdb_db_fnc_save", 2, false] (Multiplayer Only)
|
||||||
*
|
*
|
||||||
* Public: Yes
|
* Public: Yes
|
||||||
*/
|
*/
|
||||||
|
|
||||||
"ArmaRAMDb" callExtension ["save", []];
|
params [["_type", "xml", [""]], ["_createBackup", false, [false]]];
|
||||||
|
|
||||||
|
if (_type == "xml") then {
|
||||||
|
"ArmaRAMDb" callExtension ["save", []];
|
||||||
|
} else {
|
||||||
|
"ArmaRAMDb" callExtension ["saverdb", [_createBackup]];
|
||||||
|
};
|
12
config.xml
12
config.xml
@ -1,5 +1,13 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<ArmaRAMDb>
|
<ArmaRAMDb>
|
||||||
<Context>false</Context>
|
<!-- Context logging enabled -->
|
||||||
<Debug>false</Debug>
|
<Setting>true</Setting>
|
||||||
|
<!-- Debug mode enabled -->
|
||||||
|
<Setting>true</Setting>
|
||||||
|
<!-- Auto backup enabled -->
|
||||||
|
<Setting>true</Setting>
|
||||||
|
<!-- Backup frequency in minutes -->
|
||||||
|
<Setting>60</Setting>
|
||||||
|
<!-- Maximum number of backups to keep -->
|
||||||
|
<Setting>10</Setting>
|
||||||
</ArmaRAMDb>
|
</ArmaRAMDb>
|
Binary file not shown.
Binary file not shown.
@ -51,14 +51,47 @@ namespace ArmaRAMDb
|
|||||||
|
|
||||||
if (File.Exists(Environment.CurrentDirectory + "\\" + str))
|
if (File.Exists(Environment.CurrentDirectory + "\\" + str))
|
||||||
{
|
{
|
||||||
List<string> strList = [];
|
try
|
||||||
List<string> list = XElement.Load(Environment.CurrentDirectory + "\\" + str).Elements().Select(eintrag => (string)eintrag).ToList();
|
{
|
||||||
if (bool.TryParse(list[0], out bool res0))
|
var configXml = XElement.Load(Environment.CurrentDirectory + "\\" + str);
|
||||||
ARDB_CONTEXTLOG = res0;
|
List<string> settings = configXml.Elements().Select(element => (string)element).ToList();
|
||||||
if (bool.TryParse(list[1], out bool res1))
|
|
||||||
ARDB_DEBUG = res1;
|
// Parse existing settings
|
||||||
|
if (settings.Count >= 2)
|
||||||
Log($"Config file found! Context Mode: {ARDB_CONTEXTLOG}! Debug Mode: {ARDB_DEBUG}!", "action");
|
{
|
||||||
|
if (bool.TryParse(settings[0], out bool res0))
|
||||||
|
ARDB_CONTEXTLOG = res0;
|
||||||
|
if (bool.TryParse(settings[1], out bool res1))
|
||||||
|
ARDB_DEBUG = res1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse new backup settings
|
||||||
|
if (settings.Count >= 5)
|
||||||
|
{
|
||||||
|
if (bool.TryParse(settings[2], out bool autoBackup))
|
||||||
|
RAMDb.AutoBackupEnabled = autoBackup;
|
||||||
|
|
||||||
|
if (int.TryParse(settings[3], out int backupFreq) && backupFreq > 0)
|
||||||
|
RAMDb.BackupFrequencyMinutes = backupFreq;
|
||||||
|
|
||||||
|
if (int.TryParse(settings[4], out int maxBackups) && maxBackups > 0)
|
||||||
|
RAMDb.MaxBackupsToKeep = maxBackups;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log($"Config file found! Context Mode: {ARDB_CONTEXTLOG}! Debug Mode: {ARDB_DEBUG}! " +
|
||||||
|
$"Auto Backup: {RAMDb.AutoBackupEnabled} (every {RAMDb.BackupFrequencyMinutes} min, keep {RAMDb.MaxBackupsToKeep})", "action");
|
||||||
|
|
||||||
|
// Initialize automatic backup if enabled
|
||||||
|
if (RAMDb.AutoBackupEnabled)
|
||||||
|
{
|
||||||
|
RAMDb.InitializeAutoBackup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log($"Error reading config file: {ex.Message}", "error");
|
||||||
|
Log("Default Settings Loaded.", "action");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -269,6 +302,14 @@ namespace ArmaRAMDb
|
|||||||
HandleListSetOperation(args, argc);
|
HandleListSetOperation(args, argc);
|
||||||
WriteOutput(output, "Async");
|
WriteOutput(output, "Async");
|
||||||
return 200;
|
return 200;
|
||||||
|
case "loadrdb":
|
||||||
|
LoadRdb();
|
||||||
|
WriteOutput(output, "Data loaded");
|
||||||
|
return 100;
|
||||||
|
case "saverdb":
|
||||||
|
SaveRdb(args);
|
||||||
|
WriteOutput(output, "Data saved");
|
||||||
|
return 100;
|
||||||
case "load":
|
case "load":
|
||||||
LoadData();
|
LoadData();
|
||||||
WriteOutput(output, "Data loaded");
|
WriteOutput(output, "Data loaded");
|
||||||
@ -284,8 +325,20 @@ namespace ArmaRAMDb
|
|||||||
case "version":
|
case "version":
|
||||||
WriteOutput(output, ARDB_VERSION);
|
WriteOutput(output, ARDB_VERSION);
|
||||||
return 100;
|
return 100;
|
||||||
|
case "listbackups":
|
||||||
|
string backupsList = HandleListBackupsOperation();
|
||||||
|
WriteOutput(output, backupsList);
|
||||||
|
return 100;
|
||||||
|
case "restorebackup":
|
||||||
|
bool success = HandleRestoreBackupOperation(args);
|
||||||
|
WriteOutput(output, success.ToString().ToLower());
|
||||||
|
return 100;
|
||||||
|
case "deletebackup":
|
||||||
|
bool deleted = HandleDeleteBackupOperation(args);
|
||||||
|
WriteOutput(output, deleted.ToString().ToLower());
|
||||||
|
return 100;
|
||||||
default:
|
default:
|
||||||
WriteOutput(output, "Available functions: del, get, hdel, hdelid, hget, hgetid, hgetall, hgetallid, hrem, hremid, hset, hsetid, isloaded, listadd, listidx, listrem, listrng, listset, load, save, set, version");
|
WriteOutput(output, "Available functions: del, get, hdel, hdelid, hget, hgetid, hgetall, hgetallid, hrem, hremid, hset, hsetid, isloaded, listadd, listidx, listrem, listrng, listset, load, save, set, version, listbackups, restorebackup, deletebackup");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -325,6 +378,27 @@ namespace ArmaRAMDb
|
|||||||
Marshal.Copy(bytes, 0, (nint)output, bytes.Length);
|
Marshal.Copy(bytes, 0, (nint)output, bytes.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void SaveRdb(List<string> args)
|
||||||
|
{
|
||||||
|
var db = new RAMDb();
|
||||||
|
|
||||||
|
// Convert string to boolean properly
|
||||||
|
bool createBackup = args.Count > 0 &&
|
||||||
|
(args[0].Trim('"').Equals("true", StringComparison.CurrentCultureIgnoreCase) || args[0].Trim('"') == "1");
|
||||||
|
|
||||||
|
db.ExportToRdb(createBackup);
|
||||||
|
Log($"Data saved to RDB{(createBackup ? " with backup" : "")}", "action");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void LoadRdb()
|
||||||
|
{
|
||||||
|
var db = new RAMDb();
|
||||||
|
|
||||||
|
db.ImportFromRdb();
|
||||||
|
ARDB_ISLOADED = true;
|
||||||
|
Log("Data loaded from RDB", "action");
|
||||||
|
}
|
||||||
|
|
||||||
private static void SaveData()
|
private static void SaveData()
|
||||||
{
|
{
|
||||||
var db = new RAMDb();
|
var db = new RAMDb();
|
||||||
@ -579,5 +653,81 @@ namespace ArmaRAMDb
|
|||||||
await KeyValueStore.DeleteAsync(args[0].Trim('"'));
|
await KeyValueStore.DeleteAsync(args[0].Trim('"'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string HandleListBackupsOperation()
|
||||||
|
{
|
||||||
|
var db = new RAMDb();
|
||||||
|
var backups = db.ListBackups();
|
||||||
|
|
||||||
|
if (backups.Count > 0)
|
||||||
|
{
|
||||||
|
// Format backup list for Arma by joining filenames with proper array syntax
|
||||||
|
var backupFileNames = backups.Select(Path.GetFileName).ToList();
|
||||||
|
var formattedResult = string.Join("\",\"", backupFileNames);
|
||||||
|
|
||||||
|
Log($"Listed {backups.Count} available backups", "action");
|
||||||
|
return $"[\"{formattedResult}\"]";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Return empty array if no backups exist
|
||||||
|
Log("No backups available", "action");
|
||||||
|
return "[]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool HandleRestoreBackupOperation(List<string> args)
|
||||||
|
{
|
||||||
|
if (args.Count < 1) return false;
|
||||||
|
|
||||||
|
string backupFileName = args[0].Trim('"');
|
||||||
|
var db = new RAMDb();
|
||||||
|
var backups = db.ListBackups();
|
||||||
|
|
||||||
|
// Find the full path based on filename
|
||||||
|
var backupPath = backups.FirstOrDefault(b => Path.GetFileName(b).Equals(backupFileName, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
if (backupPath != null)
|
||||||
|
{
|
||||||
|
bool success = RAMDb.RestoreFromBackup(backupPath);
|
||||||
|
Log($"Restore from backup {backupFileName}: {(success ? "successful" : "failed")}", "action");
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log($"Backup {backupFileName} not found", "error");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool HandleDeleteBackupOperation(List<string> args)
|
||||||
|
{
|
||||||
|
if (args.Count < 1) return false;
|
||||||
|
|
||||||
|
string backupFileName = args[0].Trim('"');
|
||||||
|
var db = new RAMDb();
|
||||||
|
var backups = db.ListBackups();
|
||||||
|
|
||||||
|
// Find the full path based on filename
|
||||||
|
var backupPath = backups.FirstOrDefault(b => Path.GetFileName(b).Equals(backupFileName, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
if (backupPath != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.Delete(backupPath);
|
||||||
|
Log($"Deleted backup: {backupFileName}", "action");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log($"Failed to delete backup {backupFileName}: {ex.Message}", "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log($"Backup {backupFileName} not found", "error");
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,20 +5,303 @@ using System.Collections.Concurrent;
|
|||||||
namespace ArmaRAMDb
|
namespace ArmaRAMDb
|
||||||
#pragma warning restore IDE0130 // Namespace does not match folder structure
|
#pragma warning restore IDE0130 // Namespace does not match folder structure
|
||||||
{
|
{
|
||||||
internal class RAMDb(string path = RAMDb.DEFAULT_STORAGE_PATH) : IDisposable
|
internal class RAMDb(string path = RAMDb.DEFAULT_XML_PATH, string rdbPath = RAMDb.DEFAULT_RDB_PATH) : IDisposable
|
||||||
{
|
{
|
||||||
private const string DEFAULT_STORAGE_PATH = "@ramdb\\ArmaRAMDb.xml";
|
private const string DEFAULT_XML_PATH = "@ramdb\\ArmaRAMDb.xml";
|
||||||
private readonly string _storagePath = Path.Combine(Environment.CurrentDirectory, path);
|
private const string DEFAULT_RDB_PATH = "@ramdb\\ArmaRAMDb.rdb";
|
||||||
|
private readonly string _xmlPath = Path.Combine(Environment.CurrentDirectory, path);
|
||||||
|
private readonly string _rdbPath = Path.Combine(Environment.CurrentDirectory, rdbPath);
|
||||||
private XDocument _document;
|
private XDocument _document;
|
||||||
public static readonly ConcurrentDictionary<string, string> _keyValues = new();
|
public static readonly ConcurrentDictionary<string, string> _keyValues = new();
|
||||||
public static readonly ConcurrentDictionary<string, ConcurrentDictionary<string, string>> _hashTables = new();
|
public static readonly ConcurrentDictionary<string, ConcurrentDictionary<string, string>> _hashTables = new();
|
||||||
public static readonly ConcurrentDictionary<string, List<string>> _lists = new();
|
public static readonly ConcurrentDictionary<string, List<string>> _lists = new();
|
||||||
|
|
||||||
|
// Add these properties to the RAMDb class
|
||||||
|
public static bool AutoBackupEnabled { get; set; } = false;
|
||||||
|
public static int BackupFrequencyMinutes { get; set; } = 60; // Default 1 hour
|
||||||
|
public static int MaxBackupsToKeep { get; set; } = 10;
|
||||||
|
private static Timer _backupTimer;
|
||||||
|
|
||||||
|
public void ImportFromRdb()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(_rdbPath))
|
||||||
|
{
|
||||||
|
using var stream = new FileStream(_rdbPath, FileMode.Open);
|
||||||
|
using var reader = new BinaryReader(stream);
|
||||||
|
|
||||||
|
// Read version (for future compatibility)
|
||||||
|
int version = reader.ReadInt32();
|
||||||
|
if (version != 1)
|
||||||
|
{
|
||||||
|
Main.Log($"Unsupported RDB format version: {version}", "warning");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear existing collections
|
||||||
|
_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<string, string>();
|
||||||
|
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<string>();
|
||||||
|
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("RDB import complete", "debug");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Main.Log($"Error during RDB import: {ex.Message}", "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ExportToRdb(bool createBackup = false)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Save to the standard location
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(_rdbPath));
|
||||||
|
|
||||||
|
using (var stream = new FileStream(_rdbPath, FileMode.Create))
|
||||||
|
using (var writer = new BinaryWriter(stream))
|
||||||
|
{
|
||||||
|
// Add version number
|
||||||
|
writer.Write(1); // Version 1 of format
|
||||||
|
|
||||||
|
// Write data as before
|
||||||
|
WriteDataToBinaryWriter(writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a backup copy with timestamp if requested
|
||||||
|
if (createBackup)
|
||||||
|
{
|
||||||
|
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
|
||||||
|
string backupDirectory = Path.Combine(Path.GetDirectoryName(_rdbPath), "backups");
|
||||||
|
string backupFileName = Path.GetFileNameWithoutExtension(_rdbPath) + "_" + timestamp + Path.GetExtension(_rdbPath);
|
||||||
|
string backupPath = Path.Combine(backupDirectory, backupFileName);
|
||||||
|
|
||||||
|
Directory.CreateDirectory(backupDirectory);
|
||||||
|
|
||||||
|
using (var stream = new FileStream(backupPath, FileMode.Create))
|
||||||
|
using (var writer = new BinaryWriter(stream))
|
||||||
|
{
|
||||||
|
// Write the same data to the backup file
|
||||||
|
WriteDataToBinaryWriter(writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
Main.Log($"Created backup at: {backupPath}", "debug");
|
||||||
|
}
|
||||||
|
|
||||||
|
Main.Log("RDB export complete", "debug");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Main.Log($"Error during RDB export: {ex.Message}", "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the data writing logic to a separate method to avoid code duplication
|
||||||
|
private static void WriteDataToBinaryWriter(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
// Write KeyValues
|
||||||
|
writer.Write(_keyValues.Count);
|
||||||
|
foreach (var pair in _keyValues)
|
||||||
|
{
|
||||||
|
writer.Write(pair.Key);
|
||||||
|
writer.Write(pair.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write HashTables
|
||||||
|
writer.Write(_hashTables.Count);
|
||||||
|
foreach (var table in _hashTables)
|
||||||
|
{
|
||||||
|
writer.Write(table.Key);
|
||||||
|
writer.Write(table.Value.Count);
|
||||||
|
|
||||||
|
foreach (var entry in table.Value)
|
||||||
|
{
|
||||||
|
writer.Write(entry.Key);
|
||||||
|
writer.Write(entry.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write Lists
|
||||||
|
writer.Write(_lists.Count);
|
||||||
|
foreach (var list in _lists)
|
||||||
|
{
|
||||||
|
writer.Write(list.Key);
|
||||||
|
writer.Write(list.Value.Count);
|
||||||
|
|
||||||
|
foreach (var item in list.Value)
|
||||||
|
{
|
||||||
|
writer.Write(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a method to list available backups
|
||||||
|
public List<string> ListBackups()
|
||||||
|
{
|
||||||
|
string backupDirectory = Path.Combine(Path.GetDirectoryName(_rdbPath), "backups");
|
||||||
|
List<string> backups = [];
|
||||||
|
|
||||||
|
if (Directory.Exists(backupDirectory))
|
||||||
|
{
|
||||||
|
backups = [.. Directory.GetFiles(backupDirectory, "*.rdb").OrderByDescending(file => file)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return backups;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a method to restore from a specific backup
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Read version (for future compatibility)
|
||||||
|
int version = reader.ReadInt32();
|
||||||
|
if (version != 1)
|
||||||
|
{
|
||||||
|
Main.Log($"Unsupported RDB format version in backup: {version}", "warning");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear existing collections
|
||||||
|
_keyValues.Clear();
|
||||||
|
_hashTables.Clear();
|
||||||
|
_lists.Clear();
|
||||||
|
|
||||||
|
// Call a shared method for reading data
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a shared method for reading data (similar to WriteDataToBinaryWriter)
|
||||||
|
private static void ReadDataFromBinaryReader(BinaryReader reader)
|
||||||
|
{
|
||||||
|
// 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<string, string>();
|
||||||
|
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<string>();
|
||||||
|
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("RDB import complete", "debug");
|
||||||
|
}
|
||||||
|
|
||||||
public void ImportFromXml()
|
public void ImportFromXml()
|
||||||
{
|
{
|
||||||
if (File.Exists(_storagePath))
|
if (File.Exists(_xmlPath))
|
||||||
{
|
{
|
||||||
_document = XDocument.Load(_storagePath);
|
_document = XDocument.Load(_xmlPath);
|
||||||
LoadIntoMemory();
|
LoadIntoMemory();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -101,13 +384,74 @@ namespace ArmaRAMDb
|
|||||||
|
|
||||||
private void SaveDocument()
|
private void SaveDocument()
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(_storagePath));
|
Directory.CreateDirectory(Path.GetDirectoryName(_xmlPath));
|
||||||
_document.Save(_storagePath);
|
_document.Save(_xmlPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add a method to start the automatic backup timer
|
||||||
|
public static void InitializeAutoBackup()
|
||||||
|
{
|
||||||
|
if (AutoBackupEnabled)
|
||||||
|
{
|
||||||
|
_backupTimer?.Dispose();
|
||||||
|
|
||||||
|
_backupTimer = new Timer(BackupTimerCallback, null,
|
||||||
|
TimeSpan.Zero,
|
||||||
|
TimeSpan.FromMinutes(BackupFrequencyMinutes));
|
||||||
|
|
||||||
|
Main.Log($"Automatic backup initialized (every {BackupFrequencyMinutes} minutes)", "info");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timer callback method
|
||||||
|
private static void BackupTimerCallback(object state)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Create a new instance to perform the backup
|
||||||
|
var db = new RAMDb();
|
||||||
|
db.ExportToRdb(true);
|
||||||
|
|
||||||
|
// Manage backup rotation
|
||||||
|
ManageBackupRotation();
|
||||||
|
|
||||||
|
Main.Log($"Automatic backup created at {DateTime.Now}", "info");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Main.Log($"Automatic backup failed: {ex.Message}", "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to clean up old backups
|
||||||
|
private static void ManageBackupRotation()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var db = new RAMDb();
|
||||||
|
var backups = db.ListBackups();
|
||||||
|
|
||||||
|
// Keep only the number of backups specified in config
|
||||||
|
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()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
_backupTimer?.Dispose();
|
||||||
ExportToXml();
|
ExportToXml();
|
||||||
|
ExportToRdb(createBackup: true); // Create a backup on normal shutdown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user