156 lines
5.4 KiB
C#
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;
|
|
}
|
|
}
|