|
|
@ -15,7 +15,6 @@ namespace StabilityMatrix.Core.Helper; |
|
|
|
public record struct ArchiveInfo(ulong Size, ulong CompressedSize); |
|
|
|
public record struct ArchiveInfo(ulong Size, ulong CompressedSize); |
|
|
|
|
|
|
|
|
|
|
|
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] |
|
|
|
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] |
|
|
|
|
|
|
|
|
|
|
|
public static partial class ArchiveHelper |
|
|
|
public static partial class ArchiveHelper |
|
|
|
{ |
|
|
|
{ |
|
|
|
private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); |
|
|
|
private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); |
|
|
@ -59,7 +58,7 @@ public static partial class ArchiveHelper |
|
|
|
|
|
|
|
|
|
|
|
public static async Task<ArchiveInfo> TestArchive(string archivePath) |
|
|
|
public static async Task<ArchiveInfo> TestArchive(string archivePath) |
|
|
|
{ |
|
|
|
{ |
|
|
|
var process = ProcessRunner.StartAnsiProcess(SevenZipPath, new[] {"t", archivePath}); |
|
|
|
var process = ProcessRunner.StartAnsiProcess(SevenZipPath, new[] { "t", archivePath }); |
|
|
|
await process.WaitForExitAsync(); |
|
|
|
await process.WaitForExitAsync(); |
|
|
|
var output = await process.StandardOutput.ReadToEndAsync(); |
|
|
|
var output = await process.StandardOutput.ReadToEndAsync(); |
|
|
|
var matches = Regex7ZOutput().Matches(output); |
|
|
|
var matches = Regex7ZOutput().Matches(output); |
|
|
@ -74,11 +73,15 @@ public static partial class ArchiveHelper |
|
|
|
var sourceParent = Directory.GetParent(sourceDirectory)?.FullName ?? ""; |
|
|
|
var sourceParent = Directory.GetParent(sourceDirectory)?.FullName ?? ""; |
|
|
|
// We must pass in as `directory\` for archive path to be correct |
|
|
|
// We must pass in as `directory\` for archive path to be correct |
|
|
|
var sourceDirName = new DirectoryInfo(sourceDirectory).Name; |
|
|
|
var sourceDirName = new DirectoryInfo(sourceDirectory).Name; |
|
|
|
var process = ProcessRunner.StartAnsiProcess(SevenZipPath, new[] |
|
|
|
|
|
|
|
{ |
|
|
|
var result = await ProcessRunner |
|
|
|
"a", archivePath, sourceDirName + @"\", "-y" |
|
|
|
.GetProcessResultAsync( |
|
|
|
}, workingDirectory: sourceParent); |
|
|
|
SevenZipPath, |
|
|
|
await ProcessRunner.WaitForExitConditionAsync(process); |
|
|
|
new[] { "a", archivePath, sourceDirName + @"\", "-y" }, |
|
|
|
|
|
|
|
workingDirectory: sourceParent |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
.ConfigureAwait(false); |
|
|
|
|
|
|
|
result.EnsureSuccessExitCode(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public static async Task<ArchiveInfo> Extract7Z(string archivePath, string extractDirectory) |
|
|
|
public static async Task<ArchiveInfo> Extract7Z(string archivePath, string extractDirectory) |
|
|
@ -113,12 +116,17 @@ public static partial class ArchiveHelper |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public static async Task<ArchiveInfo> Extract7Z(string archivePath, string extractDirectory, IProgress<ProgressReport> progress) |
|
|
|
public static async Task<ArchiveInfo> Extract7Z( |
|
|
|
|
|
|
|
string archivePath, |
|
|
|
|
|
|
|
string extractDirectory, |
|
|
|
|
|
|
|
IProgress<ProgressReport> progress |
|
|
|
|
|
|
|
) |
|
|
|
{ |
|
|
|
{ |
|
|
|
var outputStore = new StringBuilder(); |
|
|
|
var outputStore = new StringBuilder(); |
|
|
|
var onOutput = new Action<string?>(s => |
|
|
|
var onOutput = new Action<string?>(s => |
|
|
|
{ |
|
|
|
{ |
|
|
|
if (s == null) return; |
|
|
|
if (s == null) |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
|
|
|
|
// Parse progress |
|
|
|
// Parse progress |
|
|
|
Logger.Trace($"7z: {s}"); |
|
|
|
Logger.Trace($"7z: {s}"); |
|
|
@ -128,7 +136,14 @@ public static partial class ArchiveHelper |
|
|
|
{ |
|
|
|
{ |
|
|
|
var percent = int.Parse(match.Groups[1].Value); |
|
|
|
var percent = int.Parse(match.Groups[1].Value); |
|
|
|
var currentFile = match.Groups[2].Value; |
|
|
|
var currentFile = match.Groups[2].Value; |
|
|
|
progress.Report(new ProgressReport(percent / (float) 100, "Extracting", currentFile, type: ProgressType.Extract)); |
|
|
|
progress.Report( |
|
|
|
|
|
|
|
new ProgressReport( |
|
|
|
|
|
|
|
percent / (float)100, |
|
|
|
|
|
|
|
"Extracting", |
|
|
|
|
|
|
|
currentFile, |
|
|
|
|
|
|
|
type: ProgressType.Extract |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
}); |
|
|
|
}); |
|
|
|
progress.Report(new ProgressReport(-1, isIndeterminate: true, type: ProgressType.Extract)); |
|
|
|
progress.Report(new ProgressReport(-1, isIndeterminate: true, type: ProgressType.Extract)); |
|
|
@ -216,7 +231,11 @@ public static partial class ArchiveHelper |
|
|
|
/// <param name="progress"></param> |
|
|
|
/// <param name="progress"></param> |
|
|
|
/// <param name="archivePath"></param> |
|
|
|
/// <param name="archivePath"></param> |
|
|
|
/// <param name="outputDirectory">Output directory, created if does not exist.</param> |
|
|
|
/// <param name="outputDirectory">Output directory, created if does not exist.</param> |
|
|
|
public static async Task Extract(string archivePath, string outputDirectory, IProgress<ProgressReport>? progress = default) |
|
|
|
public static async Task Extract( |
|
|
|
|
|
|
|
string archivePath, |
|
|
|
|
|
|
|
string outputDirectory, |
|
|
|
|
|
|
|
IProgress<ProgressReport>? progress = default |
|
|
|
|
|
|
|
) |
|
|
|
{ |
|
|
|
{ |
|
|
|
Directory.CreateDirectory(outputDirectory); |
|
|
|
Directory.CreateDirectory(outputDirectory); |
|
|
|
progress?.Report(new ProgressReport(-1, isIndeterminate: true)); |
|
|
|
progress?.Report(new ProgressReport(-1, isIndeterminate: true)); |
|
|
@ -229,11 +248,12 @@ public static partial class ArchiveHelper |
|
|
|
// If not available, use the size of the archive file |
|
|
|
// If not available, use the size of the archive file |
|
|
|
if (total == 0) |
|
|
|
if (total == 0) |
|
|
|
{ |
|
|
|
{ |
|
|
|
total = (ulong) new FileInfo(archivePath).Length; |
|
|
|
total = (ulong)new FileInfo(archivePath).Length; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Create an DispatchTimer that monitors the progress of the extraction |
|
|
|
// Create an DispatchTimer that monitors the progress of the extraction |
|
|
|
var progressMonitor = progress switch { |
|
|
|
var progressMonitor = progress switch |
|
|
|
|
|
|
|
{ |
|
|
|
null => null, |
|
|
|
null => null, |
|
|
|
_ => new Timer(TimeSpan.FromMilliseconds(36)) |
|
|
|
_ => new Timer(TimeSpan.FromMilliseconds(36)) |
|
|
|
}; |
|
|
|
}; |
|
|
@ -242,34 +262,38 @@ public static partial class ArchiveHelper |
|
|
|
{ |
|
|
|
{ |
|
|
|
progressMonitor.Elapsed += (_, _) => |
|
|
|
progressMonitor.Elapsed += (_, _) => |
|
|
|
{ |
|
|
|
{ |
|
|
|
if (count == 0) return; |
|
|
|
if (count == 0) |
|
|
|
|
|
|
|
return; |
|
|
|
progress!.Report(new ProgressReport(count, total, message: "Extracting")); |
|
|
|
progress!.Report(new ProgressReport(count, total, message: "Extracting")); |
|
|
|
}; |
|
|
|
}; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
await Task.Factory.StartNew(() => |
|
|
|
await Task.Factory.StartNew( |
|
|
|
{ |
|
|
|
() => |
|
|
|
var extractOptions = new ExtractionOptions |
|
|
|
|
|
|
|
{ |
|
|
|
{ |
|
|
|
Overwrite = true, |
|
|
|
var extractOptions = new ExtractionOptions |
|
|
|
ExtractFullPath = true, |
|
|
|
{ |
|
|
|
}; |
|
|
|
Overwrite = true, |
|
|
|
using var stream = File.OpenRead(archivePath); |
|
|
|
ExtractFullPath = true, |
|
|
|
using var archive = ReaderFactory.Open(stream); |
|
|
|
}; |
|
|
|
|
|
|
|
using var stream = File.OpenRead(archivePath); |
|
|
|
|
|
|
|
using var archive = ReaderFactory.Open(stream); |
|
|
|
|
|
|
|
|
|
|
|
// Start the progress reporting timer |
|
|
|
// Start the progress reporting timer |
|
|
|
progressMonitor?.Start(); |
|
|
|
progressMonitor?.Start(); |
|
|
|
|
|
|
|
|
|
|
|
while (archive.MoveToNextEntry()) |
|
|
|
while (archive.MoveToNextEntry()) |
|
|
|
{ |
|
|
|
|
|
|
|
var entry = archive.Entry; |
|
|
|
|
|
|
|
if (!entry.IsDirectory) |
|
|
|
|
|
|
|
{ |
|
|
|
{ |
|
|
|
count += (ulong) entry.CompressedSize; |
|
|
|
var entry = archive.Entry; |
|
|
|
|
|
|
|
if (!entry.IsDirectory) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
count += (ulong)entry.CompressedSize; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
archive.WriteEntryToDirectory(outputDirectory, extractOptions); |
|
|
|
} |
|
|
|
} |
|
|
|
archive.WriteEntryToDirectory(outputDirectory, extractOptions); |
|
|
|
}, |
|
|
|
} |
|
|
|
TaskCreationOptions.LongRunning |
|
|
|
}, TaskCreationOptions.LongRunning); |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
progress?.Report(new ProgressReport(progress: 1, message: "Done extracting")); |
|
|
|
progress?.Report(new ProgressReport(progress: 1, message: "Done extracting")); |
|
|
|
progressMonitor?.Stop(); |
|
|
|
progressMonitor?.Stop(); |
|
|
@ -328,14 +352,18 @@ public static partial class ArchiveHelper |
|
|
|
{ |
|
|
|
{ |
|
|
|
// Not sure why but symlink entries have a key that ends with a space |
|
|
|
// Not sure why but symlink entries have a key that ends with a space |
|
|
|
// and some broken path suffix, so we'll remove everything after the last space |
|
|
|
// and some broken path suffix, so we'll remove everything after the last space |
|
|
|
Logger.Debug($"Checking if output path {outputPath} contains space char: {outputPath.Contains(' ')}"); |
|
|
|
Logger.Debug( |
|
|
|
|
|
|
|
$"Checking if output path {outputPath} contains space char: {outputPath.Contains(' ')}" |
|
|
|
|
|
|
|
); |
|
|
|
if (outputPath.Contains(' ')) |
|
|
|
if (outputPath.Contains(' ')) |
|
|
|
{ |
|
|
|
{ |
|
|
|
outputPath = outputPath[..outputPath.LastIndexOf(' ')]; |
|
|
|
outputPath = outputPath[..outputPath.LastIndexOf(' ')]; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Logger.Debug($"Extracting symbolic link [{entry.Key.ToRepr()}] " + |
|
|
|
Logger.Debug( |
|
|
|
$"({outputPath.ToRepr()} to {entry.LinkTarget.ToRepr()})"); |
|
|
|
$"Extracting symbolic link [{entry.Key.ToRepr()}] " |
|
|
|
|
|
|
|
+ $"({outputPath.ToRepr()} to {entry.LinkTarget.ToRepr()})" |
|
|
|
|
|
|
|
); |
|
|
|
// Try to write link, if fail, continue copy file |
|
|
|
// Try to write link, if fail, continue copy file |
|
|
|
try |
|
|
|
try |
|
|
|
{ |
|
|
|
{ |
|
|
@ -346,7 +374,9 @@ public static partial class ArchiveHelper |
|
|
|
} |
|
|
|
} |
|
|
|
catch (IOException e) |
|
|
|
catch (IOException e) |
|
|
|
{ |
|
|
|
{ |
|
|
|
Logger.Warn($"Could not extract symbolic link, copying file instead: {e.Message}"); |
|
|
|
Logger.Warn( |
|
|
|
|
|
|
|
$"Could not extract symbolic link, copying file instead: {e.Message}" |
|
|
|
|
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|