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; return;
} }
if (!await IsPandocExecutableValidAsync(stagedPandocExecutable)) LOG.LogInformation("Found Pandoc executable in downloaded archive: '{Executable}'.", 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;
}
await ReplaceInstallationDirectoryAsync(stagingDir, installDir); await ReplaceInstallationDirectoryAsync(stagingDir, installDir);
await MessageBus.INSTANCE.SendSuccess(new(Icons.Material.Filled.CheckCircle, string.Format(TB("Pandoc v{0} was installed successfully."), latestVersion))); 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) 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."); LOG.LogError(ex, "An error occurred while installing Pandoc.");
} }
finally finally
@ -274,73 +270,44 @@ public static partial class Pandoc
{ {
var backupDir = $"{installDir}.backup-{Guid.NewGuid():N}"; var backupDir = $"{installDir}.backup-{Guid.NewGuid():N}";
var hasBackup = false; var hasBackup = false;
var stagingWasMoved = false;
try try
{ {
if (Directory.Exists(installDir)) if (Directory.Exists(installDir))
{ {
Directory.Move(installDir, backupDir); await MoveDirectoryWithRetriesAsync(installDir, backupDir, "moving the previous Pandoc installation to backup");
hasBackup = true; hasBackup = true;
} }
Directory.Move(stagingDir, installDir); await MoveDirectoryWithRetriesAsync(stagingDir, installDir, "moving the new Pandoc installation into place");
stagingWasMoved = true;
} }
catch (Exception ex) catch (Exception ex)
{ {
if (hasBackup && !Directory.Exists(installDir) && Directory.Exists(backupDir)) if (hasBackup && !stagingWasMoved && !Directory.Exists(installDir) && Directory.Exists(backupDir))
Directory.Move(backupDir, installDir); {
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."); LOG.LogError(ex, "Error replacing pandoc installation directory.");
throw; throw;
} }
finally finally
{ {
if (hasBackup && Directory.Exists(backupDir)) if (hasBackup && stagingWasMoved && Directory.Exists(backupDir))
await TryDeleteFolderAsync(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) private static string FindExecutableInDirectory(string rootDirectory, string executableName)
{ {
if (!Directory.Exists(rootDirectory)) if (!Directory.Exists(rootDirectory))
@ -360,19 +327,30 @@ public static partial class Pandoc
return string.Empty; 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; await RunWithRetriesAsync(
for (var attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) () =>
{
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 try
{ {
await operation(); await operation();
return; 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)); await Task.Delay(TimeSpan.FromMilliseconds(250 * attempt));
} }
} }
@ -406,7 +384,8 @@ public static partial class Pandoc
Directory.Delete(path, true); Directory.Delete(path, true);
return Task.CompletedTask; return Task.CompletedTask;
}, },
$"deleting temporary Pandoc directory '{path}'"); $"deleting temporary Pandoc directory '{path}'",
maxAttempts: 3);
} }
catch (Exception ex) catch (Exception ex)
{ {