Skip to content
Merged
12 changes: 7 additions & 5 deletions Source/NETworkManager.Settings/SettingsManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ public static void Load()
// Create a backup of the legacy XML file and delete the original
Backup(legacyFilePath,
GetSettingsBackupFolderLocation(),
$"{TimestampHelper.GetTimestamp()}_{GetLegacySettingsFileName()}");
TimestampHelper.GetTimestampFilename(GetLegacySettingsFileName()));

File.Delete(legacyFilePath);

Expand Down Expand Up @@ -279,7 +279,7 @@ private static void CreateDailyBackupIfNeeded()
// Create backup
Backup(GetSettingsFilePath(),
GetSettingsBackupFolderLocation(),
$"{TimestampHelper.GetTimestamp()}_{GetSettingsFileName()}");
TimestampHelper.GetTimestampFilename(GetSettingsFileName()));

// Cleanup old backups
CleanupBackups(GetSettingsBackupFolderLocation(),
Expand All @@ -295,21 +295,23 @@ private static void CreateDailyBackupIfNeeded()
/// specified maximum, are retained.
/// </summary>
/// <remarks>This method removes the oldest backup files first, keeping only the most recent backups as
/// determined by file creation time. It is intended to prevent excessive accumulation of backup files and manage
/// determined by the timestamp in the filename. It is intended to prevent excessive accumulation of backup files and manage
/// disk space usage.</remarks>
/// <param name="backupFolderPath">The full path to the directory containing the backup files to be managed. Cannot be null or empty.</param>
/// <param name="settingsFileName">The file name pattern used to identify backup files for cleanup.</param>
/// <param name="maxBackupFiles">The maximum number of backup files to retain. Must be greater than zero.</param>
private static void CleanupBackups(string backupFolderPath, string settingsFileName, int maxBackupFiles)
{
// Get all backup files sorted by timestamp (newest first)
var backupFiles = Directory.GetFiles(backupFolderPath)
.Where(f => f.EndsWith(settingsFileName) || f.EndsWith(GetLegacySettingsFileName()))
.OrderByDescending(f => File.GetCreationTime(f))
.Where(f => (f.EndsWith(settingsFileName) || f.EndsWith(GetLegacySettingsFileName())) && TimestampHelper.IsTimestampedFilename(Path.GetFileName(f)))
.OrderByDescending(f => TimestampHelper.ExtractTimestampFromFilename(Path.GetFileName(f)))
.ToList();

if (backupFiles.Count > maxBackupFiles)
Log.Info($"Cleaning up old backup files... Found {backupFiles.Count} backups, keeping the most recent {maxBackupFiles}.");

// Delete oldest backups until the maximum number is reached
while (backupFiles.Count > maxBackupFiles)
{
var fileToDelete = backupFiles.Last();
Expand Down
46 changes: 46 additions & 0 deletions Source/NETworkManager.Utilities/TimestampHelper.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Globalization;
using System.IO;

namespace NETworkManager.Utilities;

Expand All @@ -8,4 +10,48 @@ public static string GetTimestamp()
{
return DateTime.Now.ToString("yyyyMMddHHmmss");
}

/// <summary>
/// Generates a filename by prefixing the specified filename with a timestamp string.
/// </summary>
/// <param name="fileName">The original filename to be prefixed with a timestamp. Cannot be null or empty.</param>
/// <returns>A string containing the timestamp followed by an underscore and the original filename.</returns>
public static string GetTimestampFilename(string fileName)
{
return $"{GetTimestamp()}_{fileName}";
}

/// <summary>
/// Determines whether the specified file name begins with a valid timestamp in the format "yyyyMMddHHmmss".
/// </summary>
/// <remarks>This method checks only the first 14 characters of the file name for a valid timestamp and
/// does not validate the remainder of the file name.</remarks>
/// <param name="fileName">The file name to evaluate. The file name is expected to start with a 14-digit timestamp followed by an
/// underscore and at least one additional character.</param>
/// <returns>true if the file name starts with a valid timestamp in the format "yyyyMMddHHmmss"; otherwise, false.</returns>
public static bool IsTimestampedFilename(string fileName)
{
// Ensure the filename is long enough to contain a timestamp, an underscore, and at least one character after it
if (fileName.Length < 16)
return false;

var timestampString = fileName.Substring(0, 14);

return DateTime.TryParseExact(timestampString, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.None, out _);
}

/// <summary>
/// Extracts the timestamp from a filename that starts with a timestamp prefix.
/// </summary>
/// <remarks>Filenames are expected to start with yyyyMMddHHmmss_* format (14 characters).
/// This method extracts the timestamp portion and parses it as a DateTime.</remarks>
/// <param name="fileName">The full path to the file or just the filename.</param>
/// <returns>The timestamp extracted from the filename.</returns>
public static DateTime ExtractTimestampFromFilename(string fileName)
{
// Extract the timestamp prefix (yyyyMMddHHmmss format, 14 characters)
var timestampString = fileName.Substring(0, 14);

return DateTime.ParseExact(timestampString, "yyyyMMddHHmmss", CultureInfo.InvariantCulture);
}
}