feat: Implement automatic backup on data modification
Some checks failed
Build / build (push) Failing after 1m20s

Added automatic backup system that triggers saves when data is modified. Changes include:

- Added tracking of data modifications in BackupSystem.cs

- Added debounce timer (5s) to prevent excessive backups during rapid changes

- Modified string, list, and hash operations to track data changes

- Ensures data is saved shortly after modifications without impacting performance
This commit is contained in:
Jacob Schmidt 2025-05-03 22:57:13 -05:00
parent a03246a07d
commit 2944eac2f8
15 changed files with 127 additions and 9 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -320,6 +320,11 @@
<param name="key">The key to execute the action on</param>
<param name="action">The action to execute</param>
</member>
<member name="M:Firefly.Firefly.DisplayFireflyArt">
<summary>
Displays Firefly ASCII art in the console
</summary>
</member>
<member name="M:Firefly.Firefly.HandleAuthCommand(System.String,System.String)">
<summary>
Handles the AUTH command which authenticates a client.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -320,6 +320,11 @@
<param name="key">The key to execute the action on</param>
<param name="action">The action to execute</param>
</member>
<member name="M:Firefly.Firefly.DisplayFireflyArt">
<summary>
Displays Firefly ASCII art in the console
</summary>
</member>
<member name="M:Firefly.Firefly.HandleAuthCommand(System.String,System.String)">
<summary>
Handles the AUTH command which authenticates a client.

View File

@ -8,6 +8,13 @@ namespace Firefly
public partial class Firefly
{
#region Backup System
// Track if data has been modified since last backup
private static volatile bool dataModified = false;
// Debounce period in milliseconds for data modification backups
private static readonly int dataModificationBackupDelayMs = 5000;
// Timer for debouncing data modification backups
private static System.Timers.Timer? modificationBackupTimer;
static void InitializeBackupSystem()
{
// Create backup directory if it doesn't exist
@ -33,7 +40,21 @@ namespace Firefly
backupTimer.AutoReset = true;
backupTimer.Start();
// Set up data modification backup timer
modificationBackupTimer = new System.Timers.Timer(dataModificationBackupDelayMs);
modificationBackupTimer.Elapsed += (sender, e) =>
{
if (dataModified)
{
BackupData();
dataModified = false;
}
modificationBackupTimer.Stop(); // Stop timer after backup
};
modificationBackupTimer.AutoReset = false;
Console.WriteLine($"Automatic backup system initialized (every {autoBackupIntervalMinutes} minutes)");
Console.WriteLine($"Data modification backup system initialized (after {dataModificationBackupDelayMs/1000} seconds of inactivity)");
Console.WriteLine($"Keeping up to {maxBackupFiles} backup files");
// Register backup on exit
@ -47,6 +68,22 @@ namespace Firefly
};
}
// Call this method whenever data is modified
internal static void MarkDataAsModified()
{
if (!backupsEnabled)
return;
dataModified = true;
// Reset and start the timer to debounce multiple rapid changes
if (modificationBackupTimer != null)
{
modificationBackupTimer.Stop();
modificationBackupTimer.Start();
}
}
static void LoadDataFromBackup()
{
if (!backupsEnabled)

View File

@ -27,10 +27,11 @@ namespace Firefly
{
var hash = GetOrCreateHash(key);
bool isNewField = hash.TryAdd(field, value);
if (!isNewField)
{
hash[field] = value;
}
MarkDataAsModified();
return Encoding.UTF8.GetBytes($":{(isNewField ? 1 : 0)}\r\n");
}
@ -91,10 +92,11 @@ namespace Firefly
{
bool removed = hash.TryRemove(field, out _);
if (removed)
MarkDataAsModified();
if (hash.IsEmpty)
{
HashStoreRemove(key);
}
return Encoding.UTF8.GetBytes($":{(removed ? 1 : 0)}\r\n");
}
@ -197,6 +199,8 @@ namespace Firefly
return Encoding.UTF8.GetBytes($"-ERR internal error: {ex.Message}\r\n");
}
MarkDataAsModified();
return Encoding.UTF8.GetBytes("+OK\r\n");
}
@ -220,7 +224,12 @@ namespace Firefly
}
}
return hashStoreShards[shardIndex].GetOrAdd(key, _ => new ConcurrentDictionary<string, string>());
var hash = hashStoreShards[shardIndex].GetOrAdd(key, _ => new ConcurrentDictionary<string, string>());
if (hash.IsEmpty)
MarkDataAsModified();
return hash;
}
/// <summary>
@ -254,7 +263,12 @@ namespace Firefly
private static bool HashStoreRemove(string key)
{
int shardIndex = GetShardIndex(key);
return hashStoreShards[shardIndex].TryRemove(key, out _);
bool result = hashStoreShards[shardIndex].TryRemove(key, out _);
if (result)
MarkDataAsModified();
return result;
}
#endregion
#endregion

View File

@ -548,6 +548,13 @@ namespace Firefly
{
throw new InvalidOperationException($"Key '{key}' already exists with a different type");
}
var newList = new List<string>();
if (listStoreShards[shardIndex].TryAdd(key, newList))
{
MarkDataAsModified();
return newList;
}
}
return listStoreShards[shardIndex].GetOrAdd(key, _ => []);
@ -587,7 +594,12 @@ namespace Firefly
listStoreLocks[shardIndex].EnterWriteLock();
try
{
return listStoreShards[shardIndex].TryRemove(key, out _);
bool result = listStoreShards[shardIndex].TryRemove(key, out _);
if (result)
MarkDataAsModified();
return result;
}
finally
{
@ -608,6 +620,8 @@ namespace Firefly
{
var list = GetOrCreateList(key);
action(list);
MarkDataAsModified();
}
finally
{

View File

@ -9,6 +9,9 @@ namespace Firefly
#region Server Management
static async Task StartServerAsync()
{
// Display Firefly ASCII art
DisplayFireflyArt();
// Set up server on specified port
var listener = new TcpListener(IPAddress.Parse(bindAddress), serverPort);
listener.Start();
@ -305,6 +308,38 @@ namespace Firefly
}
}
}
/// <summary>
/// Displays Firefly ASCII art in the console
/// </summary>
static void DisplayFireflyArt()
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine(
" ,,_\n" +
" zd$$??=\n" +
" z$$P? F:`c, _\n" +
" d$$, `c'cc$$i ,cd$?R\n" +
" $$$$ cud$,?$$$i ,=P'2?z '\n" +
" $` ` ?$$$,?$$$. ,-''`>, bzP\n" +
" 'cLdb,?$$,?$$$ ,h' 'I$'J$P\n" +
" ... `?$$$,`$$,`$$h $$PxrF'd$'\n" +
" d$PP``?-,`?$$,?$h`$$,,$$'$F44'\n" +
" ?,,_`=4c,?=,`?hu?$`?L4$'? '\n" +
" ```?==``=-`` ```-`'_,,,,\n" +
" .ccu?m?e?JC,-,\"=?\n" +
" ```=='?'\n");
Console.WriteLine(@"
");
Console.ResetColor();
}
#endregion
}
}
}

View File

@ -88,6 +88,9 @@ namespace Firefly
int shardIndex = GetShardIndex(key);
stringStoreShards[shardIndex][key] = value;
MarkDataAsModified();
return true;
}
@ -111,7 +114,12 @@ namespace Firefly
private static bool StringStoreRemove(string key)
{
int shardIndex = GetShardIndex(key);
return stringStoreShards[shardIndex].TryRemove(key, out _);
bool result = stringStoreShards[shardIndex].TryRemove(key, out _);
if (result)
MarkDataAsModified();
return result;
}
#endregion
#endregion