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 } 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 { // Maybe change log level to Fatal later to stand out more Log.Logger.Error(fullMessage); } catch { ShowUserWhenLogUnavailable?.Invoke(fullMessage); } } }