using Serilog;
using System;
using System.IO;
using System.Threading;
namespace LibationFileManager;
///
/// Validates that essential files were created correctly after creation, with retries to allow for OS delay.
/// Callers should use to log errors and show the user when the log cannot be written.
///
public static class EssentialFileValidator
{
///
/// Called when an essential file validation fails and the error could not be written to the log.
/// Set by the host (e.g. LibationAvalonia, LibationWinForms) to display the message to the user.
///
public static Action? ShowUserWhenLogUnavailable { get; set; }
///
/// Default retry total duration (ms) when checking that a file is available after creation.
///
public const int DefaultMaxRetriesMs = 1000;
///
/// Default delay (ms) between retries.
///
public const int DefaultDelayMs = 50;
///
/// Validates that the file at exists and is readable and writable,
/// with retries to allow for OS delay between create and availability.
/// Error messages use the file name portion of .
///
/// Full path to the file.
/// Total time to retry (ms).
/// Delay between retries (ms).
/// (true, null) if valid; (false, errorMessage) if validation failed.
public static (bool success, string? errorMessage) ValidateCreated(
string path,
int maxRetriesMs = DefaultMaxRetriesMs,
int delayMs = DefaultDelayMs)
{
if (string.IsNullOrWhiteSpace(path))
return (false, "(unknown file): path is null or empty.");
var displayName = Path.GetFileName(path);
if (string.IsNullOrWhiteSpace(displayName))
displayName = path;
var stopAt = DateTime.UtcNow.AddMilliseconds(maxRetriesMs);
Exception? lastEx = null;
while (DateTime.UtcNow < stopAt)
{
try
{
if (!File.Exists(path))
{
lastEx = new FileNotFoundException($"File not found after creation: {path}");
Thread.Sleep(delayMs);
continue;
}
using (var fs = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.Read))
{
// ensure we can open for read and write
}
Log.Logger.Debug("Essential file validated: {DisplayName} at \"{Path}\"", displayName, path);
return (true, null);
}
catch (Exception ex)
{
lastEx = ex;
Thread.Sleep(delayMs);
}
}
var msg = lastEx is not null
? $"{displayName} could not be validated at \"{path}\": {lastEx.Message}"
: $"{displayName} could not be validated at \"{path}\" (file not found or not accessible).";
return (false, msg);
}
///
/// Validates that the file was created correctly and, if validation fails, reports the failure (log and optionally user).
/// Equivalent to calling then when the result is not valid.
///
/// True if the file is valid; false if validation failed (and failure has been reported).
public static bool ValidateCreatedAndReport(
string path,
int maxRetriesMs = DefaultMaxRetriesMs,
int delayMs = DefaultDelayMs)
{
var (success, errorMessage) = ValidateCreated(path, maxRetriesMs, delayMs);
if (!success && errorMessage is not null)
ReportValidationFailure(errorMessage);
return success;
}
///
/// Reports a validation failure: tries to log the error; if logging fails, invokes .
/// The message is prefixed with a strongly worded error notice for both log and user display.
///
/// Message to log and optionally show to the user.
public static void ReportValidationFailure(string errorMessage)
{
var fullMessage = $"Critical error! Essential file validation failed: {errorMessage}";
try
{
Log.Logger.Error("Critical error! Essential file validation failed: {ErrorMessage}. Call stack: {StackTrace}",
errorMessage, Environment.StackTrace);
}
catch
{
ShowUserWhenLogUnavailable?.Invoke(fullMessage);
}
}
}