Files
Explorer-Workbench/src/Explorer.Windows/SevenZipArchiveExecutor.cs

156 lines
5.4 KiB
C#

using System.Diagnostics;
using System.Text;
using System.Text.RegularExpressions;
using Explorer.Application;
using Explorer.Domain;
namespace Explorer.Windows;
public sealed class SevenZipArchiveExecutor : IArchiveExecutor
{
private static readonly Regex Percent = new(@"(\d{1,3})\s*%", RegexOptions.CultureInvariant);
private readonly Func<string?> _configuredPath;
public SevenZipArchiveExecutor(UiPreferencesStore preferences)
=> _configuredPath = () => preferences.Load().SevenZipPath;
public bool IsAvailable => SevenZipLocator.Find(_configuredPath()) is not null;
public string MissingHint => SevenZipLocator.MissingHint;
public Task ExtractAsync(
string archivePath,
string destinationDirectory,
IProgress<ArchiveProgress>? progress,
CancellationToken cancellationToken)
{
Directory.CreateDirectory(PathRules.ToExtended(destinationDirectory));
return RunAsync(
["x", archivePath, "-o" + destinationDirectory, "-y", "-aoa", "-bb1", "-bsp1"],
PathRules.Parent(archivePath),
progress,
cancellationToken);
}
public Task CompressAsync(
IReadOnlyList<string> sources,
string archivePath,
ArchiveFormat format,
IProgress<ArchiveProgress>? progress,
CancellationToken cancellationToken)
{
var type = format == ArchiveFormat.SevenZip ? "-t7z" : "-tzip";
var args = new List<string> { "a", type, "-y", "-bb1", "-bsp1", archivePath };
args.AddRange(RelativeSources(sources, out var workDir));
return RunAsync(args, workDir, progress, cancellationToken);
}
public Task AddAsync(
string archivePath,
IReadOnlyList<string> sources,
IProgress<ArchiveProgress>? progress,
CancellationToken cancellationToken)
{
var args = new List<string> { "a", "-y", "-bb1", "-bsp1", archivePath };
args.AddRange(RelativeSources(sources, out var workDir));
return RunAsync(args, workDir, progress, cancellationToken);
}
public Task VerifyAsync(
string archivePath,
IProgress<ArchiveProgress>? progress,
CancellationToken cancellationToken)
=> RunAsync(
["t", archivePath, "-bb1", "-bsp1"],
PathRules.Parent(archivePath),
progress,
cancellationToken);
private async Task RunAsync(
IReadOnlyList<string> arguments,
string? workingDirectory,
IProgress<ArchiveProgress>? progress,
CancellationToken cancellationToken)
{
var exe = SevenZipLocator.Find(_configuredPath())
?? throw new InvalidOperationException(MissingHint);
var psi = new ProcessStartInfo
{
FileName = exe,
WorkingDirectory = string.IsNullOrWhiteSpace(workingDirectory) ? Environment.CurrentDirectory : workingDirectory,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
StandardOutputEncoding = Encoding.UTF8,
StandardErrorEncoding = Encoding.UTF8
};
foreach (var argument in arguments)
{
psi.ArgumentList.Add(argument);
}
using var process = new Process { StartInfo = psi, EnableRaisingEvents = true };
var errors = new StringBuilder();
var files = 0L;
process.OutputDataReceived += (_, e) =>
{
if (string.IsNullOrEmpty(e.Data))
{
return;
}
var match = Percent.Match(e.Data);
if (match.Success && int.TryParse(match.Groups[1].Value, out var pct))
{
progress?.Report(new ArchiveProgress(Math.Clamp(pct, 0, 100), files, null));
}
if (e.Data.StartsWith('+') || e.Data.StartsWith('-') || e.Data.StartsWith('T'))
{
files++;
progress?.Report(new ArchiveProgress(0, files, e.Data.Trim()));
}
};
process.ErrorDataReceived += (_, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
{
errors.AppendLine(e.Data);
}
};
if (!process.Start())
{
throw new IOException("7-Zip could not be started.");
}
process.BeginOutputReadLine();
process.BeginErrorReadLine();
await using var kill = cancellationToken.Register(() =>
{
try { process.Kill(entireProcessTree: true); } catch { /* already exited */ }
});
await process.WaitForExitAsync(CancellationToken.None).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
if (process.ExitCode > 1)
{
var detail = errors.ToString().Trim();
throw new IOException(string.IsNullOrEmpty(detail) ? $"7-Zip failed ({process.ExitCode})." : detail);
}
}
private static IEnumerable<string> RelativeSources(IReadOnlyList<string> sources, out string workDir)
{
if (sources.All(s => PathRules.Parent(s).Equals(PathRules.Parent(sources[0]), StringComparison.OrdinalIgnoreCase)))
{
workDir = PathRules.Parent(sources[0]);
return sources.Select(PathRules.GetFileName).ToList();
}
workDir = PathRules.Parent(sources[0]);
return sources;
}
}