Fixed the Pandoc installation

This commit is contained in:
Thorsten Sommer 2026-05-16 17:24:14 +02:00
parent f72bbfe9c6
commit 06407ad111
Signed by untrusted user who does not match committer: tsommer
GPG Key ID: 371BBA77A02C0108

View File

@ -244,12 +244,7 @@ public static partial class Pandoc
return;
}
if (!await IsPandocExecutableValidAsync(stagedPandocExecutable))
{
await MessageBus.INSTANCE.SendError(new (Icons.Material.Filled.Error, TB("Pandoc was not installed successfully, because the downloaded executable could not be validated.")));
LOG.LogError("Pandoc was not installed, the downloaded executable could not be validated: '{Executable}'.", stagedPandocExecutable);
return;
}
LOG.LogInformation("Found Pandoc executable in downloaded archive: '{Executable}'.", stagedPandocExecutable);
await ReplaceInstallationDirectoryAsync(stagingDir, installDir);
await MessageBus.INSTANCE.SendSuccess(new(Icons.Material.Filled.CheckCircle, string.Format(TB("Pandoc v{0} was installed successfully."), latestVersion)));
@ -257,6 +252,7 @@ public static partial class Pandoc
}
catch (Exception ex)
{
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Error, TB("Pandoc was not installed successfully.")));
LOG.LogError(ex, "An error occurred while installing Pandoc.");
}
finally
@ -274,73 +270,44 @@ public static partial class Pandoc
{
var backupDir = $"{installDir}.backup-{Guid.NewGuid():N}";
var hasBackup = false;
var stagingWasMoved = false;
try
{
if (Directory.Exists(installDir))
{
Directory.Move(installDir, backupDir);
await MoveDirectoryWithRetriesAsync(installDir, backupDir, "moving the previous Pandoc installation to backup");
hasBackup = true;
}
Directory.Move(stagingDir, installDir);
await MoveDirectoryWithRetriesAsync(stagingDir, installDir, "moving the new Pandoc installation into place");
stagingWasMoved = true;
}
catch (Exception ex)
{
if (hasBackup && !Directory.Exists(installDir) && Directory.Exists(backupDir))
Directory.Move(backupDir, installDir);
if (hasBackup && !stagingWasMoved && !Directory.Exists(installDir) && Directory.Exists(backupDir))
{
try
{
await MoveDirectoryWithRetriesAsync(backupDir, installDir, "restoring the previous Pandoc installation");
hasBackup = false;
}
catch (Exception rollbackEx)
{
LOG.LogError(rollbackEx, "Error restoring previous Pandoc installation directory. Keeping backup directory at: '{BackupDir}'.", backupDir);
}
}
LOG.LogError(ex, "Error replacing pandoc installation directory.");
throw;
}
finally
{
if (hasBackup && Directory.Exists(backupDir))
if (hasBackup && stagingWasMoved && Directory.Exists(backupDir))
await TryDeleteFolderAsync(backupDir);
}
}
private static async Task<bool> IsPandocExecutableValidAsync(string executable)
{
try
{
var startInfo = new ProcessStartInfo
{
FileName = executable,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
startInfo.ArgumentList.Add("--version");
using var process = Process.Start(startInfo);
if (process is null)
return false;
var outputTask = process.StandardOutput.ReadToEndAsync();
var errorTask = process.StandardError.ReadToEndAsync();
await process.WaitForExitAsync();
var output = await outputTask;
var error = await errorTask;
if (process.ExitCode is not 0)
{
LOG.LogError("Downloaded Pandoc executable exited with code {ProcessExitCode}. Error output: '{ErrorText}'", process.ExitCode, error);
return false;
}
var versionMatch = PandocCmdRegex().Match(output);
return versionMatch.Success && Version.Parse(versionMatch.Groups[1].Value) >= MINIMUM_REQUIRED_VERSION;
}
catch (Exception ex)
{
LOG.LogError(ex, "Error validating downloaded Pandoc executable.");
return false;
}
}
private static string FindExecutableInDirectory(string rootDirectory, string executableName)
{
if (!Directory.Exists(rootDirectory))
@ -360,19 +327,30 @@ public static partial class Pandoc
return string.Empty;
}
private static async Task RunWithRetriesAsync(Func<Task> operation, string operationName)
private static async Task MoveDirectoryWithRetriesAsync(string sourceDir, string destinationDir, string operationName)
{
const int MAX_ATTEMPTS = 4;
for (var attempt = 1; attempt <= MAX_ATTEMPTS; attempt++)
await RunWithRetriesAsync(
() =>
{
Directory.Move(sourceDir, destinationDir);
return Task.CompletedTask;
},
operationName,
maxAttempts: 8);
}
private static async Task RunWithRetriesAsync(Func<Task> operation, string operationName, int maxAttempts = 4)
{
for (var attempt = 1; attempt <= maxAttempts; attempt++)
{
try
{
await operation();
return;
}
catch (Exception ex) when (attempt < MAX_ATTEMPTS && ex is IOException or UnauthorizedAccessException)
catch (Exception ex) when (attempt < maxAttempts && ex is IOException or UnauthorizedAccessException)
{
LOG.LogWarning(ex, "Error while {OperationName}; retrying attempt {Attempt}/{MaxAttempts}.", operationName, attempt + 1, MAX_ATTEMPTS);
LOG.LogWarning(ex, "Error while {OperationName}; retrying attempt {Attempt}/{MaxAttempts}.", operationName, attempt + 1, maxAttempts);
await Task.Delay(TimeSpan.FromMilliseconds(250 * attempt));
}
}
@ -406,7 +384,8 @@ public static partial class Pandoc
Directory.Delete(path, true);
return Task.CompletedTask;
},
$"deleting temporary Pandoc directory '{path}'");
$"deleting temporary Pandoc directory '{path}'",
maxAttempts: 3);
}
catch (Exception ex)
{