mirror of
https://github.com/velopack/velopack.git
synced 2025-10-25 15:19:22 +00:00
Add async/progress zip compression
This commit is contained in:
@@ -4,6 +4,7 @@ using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Velopack.Compression
|
||||
@@ -17,22 +18,30 @@ namespace Velopack.Compression
|
||||
ZipFile.ExtractToDirectory(inputFile, outputDirectory);
|
||||
}
|
||||
|
||||
public static void CreateZipFromDirectory(ILogger logger, string outputFile, string directoryToCompress, bool deterministic = true)
|
||||
public static void CreateZipFromDirectory(ILogger logger, string outputFile, string directoryToCompress, Action<int> progress = null)
|
||||
{
|
||||
progress ??= (x => { });
|
||||
logger.Info($"Compressing '{directoryToCompress}' to '{outputFile}' using System.IO.Compression...");
|
||||
|
||||
if (deterministic) {
|
||||
DeterministicCreateFromDirectory(directoryToCompress, outputFile, null, false, Encoding.UTF8);
|
||||
// we have stopped using ZipFile so we can add async and determinism.
|
||||
// ZipFile.CreateFromDirectory(directoryToCompress, outputFile);
|
||||
DeterministicCreateFromDirectory(directoryToCompress, outputFile, null, false, Encoding.UTF8, progress);
|
||||
}
|
||||
|
||||
} else {
|
||||
ZipFile.CreateFromDirectory(directoryToCompress, outputFile);
|
||||
}
|
||||
public static async Task CreateZipFromDirectoryAsync(ILogger logger, string outputFile, string directoryToCompress, Action<int> progress = null)
|
||||
{
|
||||
progress ??= (x => { });
|
||||
logger.Info($"Compressing '{directoryToCompress}' to '{outputFile}' using System.IO.Compression...");
|
||||
|
||||
// we have stopped using ZipFile so we can add async and determinism.
|
||||
// ZipFile.CreateFromDirectory(directoryToCompress, outputFile);
|
||||
await DeterministicCreateFromDirectoryAsync(directoryToCompress, outputFile, null, false, Encoding.UTF8, progress).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static char s_pathSeperator = '/';
|
||||
private static readonly DateTime ZipFormatMinDate = new DateTime(1980, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
private static void DeterministicCreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName, CompressionLevel? compressionLevel, bool includeBaseDirectory, Encoding entryNameEncoding)
|
||||
private static void DeterministicCreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName, CompressionLevel? compressionLevel, bool includeBaseDirectory, Encoding entryNameEncoding, Action<int> progress)
|
||||
{
|
||||
sourceDirectoryName = Path.GetFullPath(sourceDirectoryName);
|
||||
destinationArchiveFileName = Path.GetFullPath(destinationArchiveFileName);
|
||||
@@ -49,17 +58,74 @@ namespace Velopack.Compression
|
||||
.OrderBy(f => f.FullName)
|
||||
.ToArray();
|
||||
|
||||
foreach (FileSystemInfo item in files) {
|
||||
for (var i = 0; i < files.Length; i++) {
|
||||
var item = files[i];
|
||||
flag = false;
|
||||
int length = item.FullName.Length - fullName.Length;
|
||||
string text = EntryFromPath(item.FullName, fullName.Length, length);
|
||||
|
||||
if (item is FileInfo) {
|
||||
DoCreateEntryFromFile(zipArchive, item.FullName, text, compressionLevel);
|
||||
var sourceFileName = item.FullName;
|
||||
var entryName = text;
|
||||
using Stream stream = File.Open(sourceFileName, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
ZipArchiveEntry zipArchiveEntry = zipArchive.CreateEntry(entryName);
|
||||
zipArchiveEntry.LastWriteTime = ZipFormatMinDate;
|
||||
using (Stream destination2 = zipArchiveEntry.Open()) {
|
||||
stream.CopyTo(destination2);
|
||||
}
|
||||
} else if (item is DirectoryInfo possiblyEmptyDir && IsDirEmpty(possiblyEmptyDir)) {
|
||||
var entry = zipArchive.CreateEntry(text + s_pathSeperator);
|
||||
entry.LastWriteTime = ZipFormatMinDate;
|
||||
}
|
||||
|
||||
progress((int) ((double) i / files.Length * 100));
|
||||
}
|
||||
|
||||
if (includeBaseDirectory && flag) {
|
||||
string text = EntryFromPath(directoryInfo.Name, 0, directoryInfo.Name.Length);
|
||||
var entry = zipArchive.CreateEntry(text + s_pathSeperator);
|
||||
entry.LastWriteTime = ZipFormatMinDate;
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task DeterministicCreateFromDirectoryAsync(string sourceDirectoryName, string destinationArchiveFileName, CompressionLevel? compressionLevel, bool includeBaseDirectory, Encoding entryNameEncoding, Action<int> progress)
|
||||
{
|
||||
sourceDirectoryName = Path.GetFullPath(sourceDirectoryName);
|
||||
destinationArchiveFileName = Path.GetFullPath(destinationArchiveFileName);
|
||||
using ZipArchive zipArchive = ZipFile.Open(destinationArchiveFileName, ZipArchiveMode.Create, entryNameEncoding);
|
||||
bool flag = true;
|
||||
DirectoryInfo directoryInfo = new DirectoryInfo(sourceDirectoryName);
|
||||
string fullName = directoryInfo.FullName;
|
||||
if (includeBaseDirectory && directoryInfo.Parent != null) {
|
||||
fullName = directoryInfo.Parent.FullName;
|
||||
}
|
||||
|
||||
var files = directoryInfo
|
||||
.EnumerateFileSystemInfos("*", SearchOption.AllDirectories)
|
||||
.OrderBy(f => f.FullName)
|
||||
.ToArray();
|
||||
|
||||
for (var i = 0; i < files.Length; i++) {
|
||||
var item = files[i];
|
||||
flag = false;
|
||||
int length = item.FullName.Length - fullName.Length;
|
||||
string text = EntryFromPath(item.FullName, fullName.Length, length);
|
||||
|
||||
if (item is FileInfo) {
|
||||
var sourceFileName = item.FullName;
|
||||
var entryName = text;
|
||||
using Stream stream = File.Open(sourceFileName, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
ZipArchiveEntry zipArchiveEntry = zipArchive.CreateEntry(entryName);
|
||||
zipArchiveEntry.LastWriteTime = ZipFormatMinDate;
|
||||
using (Stream destination2 = zipArchiveEntry.Open()) {
|
||||
await stream.CopyToAsync(destination2).ConfigureAwait(false);
|
||||
}
|
||||
} else if (item is DirectoryInfo possiblyEmptyDir && IsDirEmpty(possiblyEmptyDir)) {
|
||||
var entry = zipArchive.CreateEntry(text + s_pathSeperator);
|
||||
entry.LastWriteTime = ZipFormatMinDate;
|
||||
}
|
||||
|
||||
progress((int) ((double) i / files.Length * 100));
|
||||
}
|
||||
|
||||
if (includeBaseDirectory && flag) {
|
||||
@@ -101,31 +167,5 @@ namespace Velopack.Compression
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static ZipArchiveEntry DoCreateEntryFromFile(ZipArchive destination, string sourceFileName, string entryName, CompressionLevel? compressionLevel)
|
||||
{
|
||||
if (destination == null) {
|
||||
throw new ArgumentNullException("destination");
|
||||
}
|
||||
|
||||
if (sourceFileName == null) {
|
||||
throw new ArgumentNullException("sourceFileName");
|
||||
}
|
||||
|
||||
if (entryName == null) {
|
||||
throw new ArgumentNullException("entryName");
|
||||
}
|
||||
|
||||
using Stream stream = File.Open(sourceFileName, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
ZipArchiveEntry zipArchiveEntry = (compressionLevel.HasValue ? destination.CreateEntry(entryName, compressionLevel.Value) : destination.CreateEntry(entryName));
|
||||
|
||||
zipArchiveEntry.LastWriteTime = ZipFormatMinDate;
|
||||
|
||||
using (Stream destination2 = zipArchiveEntry.Open()) {
|
||||
stream.CopyTo(destination2);
|
||||
}
|
||||
|
||||
return zipArchiveEntry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +166,21 @@ namespace Velopack
|
||||
public virtual async Task DownloadUpdatesAsync(
|
||||
UpdateInfo updates, Action<int> progress = null, bool ignoreDeltas = false, CancellationToken cancelToken = default)
|
||||
{
|
||||
|
||||
progress ??= (_ => { });
|
||||
|
||||
// the progress delegate may very likely invoke into the client main thread for UI updates, so
|
||||
// let's try to reduce the spam. report only on even numbers and only if the progress has changed.
|
||||
int lastProgress = 0;
|
||||
void reportProgress(int x)
|
||||
{
|
||||
int result = (int) (Math.Round(x / 2d, MidpointRounding.AwayFromZero) * 2d);
|
||||
if (result != lastProgress) {
|
||||
lastProgress = result;
|
||||
progress(result);
|
||||
}
|
||||
}
|
||||
|
||||
var targetRelease = updates?.TargetFullRelease;
|
||||
if (targetRelease == null) {
|
||||
throw new ArgumentException("Must pass a valid UpdateInfo object with a non-null TargetFullRelease", nameof(updates));
|
||||
@@ -210,18 +224,19 @@ namespace Velopack
|
||||
Log.Warn("No base package available. Attempting delta update using application files.");
|
||||
Utility.CopyFiles(Locator.AppContentDir, deltaStagingDir);
|
||||
}
|
||||
progress(10);
|
||||
await DownloadAndApplyDeltaUpdates(deltaStagingDir, updates, x => progress(Utility.CalculateProgress(x, 10, 90)))
|
||||
reportProgress(10);
|
||||
await DownloadAndApplyDeltaUpdates(deltaStagingDir, updates, x => reportProgress(Utility.CalculateProgress(x, 10, 80)))
|
||||
.ConfigureAwait(false);
|
||||
progress(90);
|
||||
reportProgress(80);
|
||||
|
||||
Log.Info("Delta updates completed, creating final update package.");
|
||||
File.Delete(incompleteFile);
|
||||
EasyZip.CreateZipFromDirectory(Log, incompleteFile, deltaStagingDir);
|
||||
await EasyZip.CreateZipFromDirectoryAsync(Log, incompleteFile, deltaStagingDir, x => reportProgress(Utility.CalculateProgress(x, 80, 100)))
|
||||
.ConfigureAwait(false);
|
||||
File.Delete(completeFile);
|
||||
File.Move(incompleteFile, completeFile);
|
||||
Log.Info("Delta release preparations complete. Package moved to: " + completeFile);
|
||||
progress(100);
|
||||
reportProgress(100);
|
||||
return; // success!
|
||||
}
|
||||
}
|
||||
@@ -235,13 +250,13 @@ namespace Velopack
|
||||
|
||||
Log.Info($"Downloading full release ({targetRelease.OriginalFilename})");
|
||||
File.Delete(incompleteFile);
|
||||
await Source.DownloadReleaseEntry(targetRelease, incompleteFile, progress).ConfigureAwait(false);
|
||||
await Source.DownloadReleaseEntry(targetRelease, incompleteFile, reportProgress).ConfigureAwait(false);
|
||||
Log.Info("Verifying package checksum...");
|
||||
VerifyPackageChecksum(targetRelease, incompleteFile);
|
||||
File.Delete(completeFile);
|
||||
File.Move(incompleteFile, completeFile);
|
||||
Log.Info("Full release download complete. Package moved to: " + completeFile);
|
||||
progress(100);
|
||||
reportProgress(100);
|
||||
} finally {
|
||||
if (VelopackRuntimeInfo.IsWindows) {
|
||||
try {
|
||||
@@ -252,11 +267,11 @@ namespace Velopack
|
||||
if (zip.UpdateExeBytes == null) {
|
||||
Log.Error("Update.exe not found in package, skipping extraction.");
|
||||
} else {
|
||||
#if NET5_0_OR_GREATER
|
||||
await Utility.RetryAsync(() => File.WriteAllBytesAsync(updateExe, zip.UpdateExeBytes)).ConfigureAwait(false);
|
||||
#else
|
||||
Utility.Retry(() => File.WriteAllBytes(updateExe, zip.UpdateExeBytes));
|
||||
#endif
|
||||
await Utility.RetryAsync(async () => {
|
||||
using var ms = new MemoryStream(zip.UpdateExeBytes);
|
||||
using var fs = File.Create(updateExe);
|
||||
await ms.CopyToAsync(fs).ConfigureAwait(false);
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Log.Error(ex, "Failed to extract new Update.exe");
|
||||
|
||||
Reference in New Issue
Block a user