mirror of
https://github.com/velopack/velopack.git
synced 2025-10-25 15:19:22 +00:00
Refactor helper/exe, Fix zstd bugs: don't use update binary on osx and windowLog
This commit is contained in:
@@ -11,17 +11,33 @@ pub fn patch(old_file: &PathBuf, patch_file: &PathBuf, output_file: &PathBuf) ->
|
||||
}
|
||||
|
||||
let dict = fs::read(old_file)?;
|
||||
|
||||
info!("Loading Dictionary (Size: {})", dict.len());
|
||||
let patch = fs::OpenOptions::new().read(true).open(patch_file)?;
|
||||
let patch_reader = io::BufReader::new(patch);
|
||||
let mut output = fs::OpenOptions::new().write(true).create(true).open(output_file)?;
|
||||
let mut decoder = zstd::Decoder::with_dictionary(patch_reader, &dict)?;
|
||||
|
||||
info!("Dictionary Size: {}", dict.len());
|
||||
info!("Decoder loaded. Beginning patch...");
|
||||
let window_log = fio_highbit64(dict.len() as u64) + 1;
|
||||
if window_log >= 27 {
|
||||
info!("Large File detected. Overriding windowLog to {}", window_log);
|
||||
decoder.window_log_max(window_log)?;
|
||||
}
|
||||
|
||||
info!("Decoder loaded. Beginning patch...");
|
||||
let mut output = fs::OpenOptions::new().write(true).create(true).open(output_file)?;
|
||||
io::copy(&mut decoder, &mut output)?;
|
||||
|
||||
info!("Patch applied successfully.");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fio_highbit64(v: u64) -> u32 {
|
||||
let mut count: u32 = 0;
|
||||
let mut v = v;
|
||||
v >>= 1;
|
||||
while v > 0 {
|
||||
v >>= 1;
|
||||
count += 1;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
21
src/Velopack.Packaging.Unix/AppImageTool.cs
Normal file
21
src/Velopack.Packaging.Unix/AppImageTool.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Velopack.Packaging.Unix
|
||||
{
|
||||
public class AppImageTool
|
||||
{
|
||||
[SupportedOSPlatform("linux")]
|
||||
public static void CreateLinuxAppImage(string appDir, string outputFile)
|
||||
{
|
||||
var tool = HelperFile.AppImageToolX64;
|
||||
Chmod.ChmodFileAsExecutable(tool);
|
||||
Exe.InvokeAndThrowIfNonZero(tool, new[] { appDir, outputFile }, null);
|
||||
Chmod.ChmodFileAsExecutable(outputFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ public class Chmod
|
||||
else if (VelopackRuntimeInfo.IsLinux) chmod = nix_chmod;
|
||||
else return; // no-op on windows, all .exe files can be executed.
|
||||
|
||||
var filePermissionOctal = Convert.ToInt32("777", 8);
|
||||
var filePermissionOctal = Convert.ToInt32("755", 8);
|
||||
const int EINTR = 4;
|
||||
int chmodReturnCode;
|
||||
|
||||
|
||||
@@ -60,11 +60,10 @@ Categories=Development;
|
||||
|
||||
// app icon
|
||||
File.Copy(Options.Icon, Path.Combine(dir.FullName, Options.PackId + Path.GetExtension(Options.Icon)), true);
|
||||
var helper = new HelperExe(Log);
|
||||
|
||||
// velopack required files
|
||||
File.WriteAllText(Path.Combine(bin.FullName, "sq.version"), nuspecText);
|
||||
File.Copy(helper.UpdateNixPath, Path.Combine(bin.FullName, "UpdateNix"), true);
|
||||
File.Copy(HelperFile.GetUpdatePath(), Path.Combine(bin.FullName, "UpdateNix"), true);
|
||||
progress(100);
|
||||
return Task.FromResult(dir.FullName);
|
||||
}
|
||||
@@ -72,8 +71,7 @@ Categories=Development;
|
||||
protected override Task CreatePortablePackage(Action<int> progress, string packDir, string outputPath)
|
||||
{
|
||||
progress(-1);
|
||||
var helper = new HelperExe(Log);
|
||||
helper.CreateLinuxAppImage(packDir, outputPath);
|
||||
AppImageTool.CreateLinuxAppImage(packDir, outputPath);
|
||||
PortablePackagePath = outputPath;
|
||||
progress(100);
|
||||
return Task.CompletedTask;
|
||||
|
||||
@@ -31,10 +31,9 @@ public class OsxPackCommandRunner : PackageBuilder<OsxPackOptions>
|
||||
Utility.DeleteFileOrDirectoryHard(appBundlePath);
|
||||
}
|
||||
|
||||
var helper = new HelperExe(Log);
|
||||
var macosdir = structure.MacosDirectory;
|
||||
File.WriteAllText(Path.Combine(macosdir, "sq.version"), nuspecText);
|
||||
File.Copy(helper.UpdateMacPath, Path.Combine(macosdir, "UpdateMac"), true);
|
||||
File.Copy(HelperFile.GetUpdatePath(), Path.Combine(macosdir, "UpdateMac"), true);
|
||||
|
||||
foreach (var f in Directory.GetFiles(macosdir, "*", SearchOption.AllDirectories)) {
|
||||
if (MachO.IsMachOImage(f)) {
|
||||
@@ -49,12 +48,12 @@ public class OsxPackCommandRunner : PackageBuilder<OsxPackOptions>
|
||||
|
||||
protected override Task CodeSign(Action<int> progress, string packDir)
|
||||
{
|
||||
var helper = new HelperExe(Log);
|
||||
var helper = new OsxBuildTools(Log);
|
||||
// code signing all mach-o binaries
|
||||
if (!string.IsNullOrEmpty(Options.SigningAppIdentity) && !string.IsNullOrEmpty(Options.NotaryProfile)) {
|
||||
progress(-1); // indeterminate
|
||||
var zipPath = Path.Combine(TempDir.FullName, "notarize.zip");
|
||||
helper.CodeSign(Options.SigningAppIdentity, Options.SigningEntitlements, packDir);
|
||||
helper.CodeSign(Options.SigningAppIdentity, Options.SigningEntitlements, packDir);
|
||||
helper.CreateDittoZip(packDir, zipPath);
|
||||
helper.Notarize(zipPath, Options.NotaryProfile);
|
||||
helper.Staple(packDir);
|
||||
@@ -64,7 +63,7 @@ public class OsxPackCommandRunner : PackageBuilder<OsxPackOptions>
|
||||
} else if (!string.IsNullOrEmpty(Options.SigningAppIdentity)) {
|
||||
progress(-1); // indeterminate
|
||||
Log.Warn("Package will be signed, but [underline]not notarized[/]. Missing the --notaryProfile option.");
|
||||
helper.CodeSign(Options.SigningAppIdentity, Options.SigningEntitlements, packDir);
|
||||
helper.CodeSign(Options.SigningAppIdentity, Options.SigningEntitlements, packDir);
|
||||
progress(100);
|
||||
} else {
|
||||
Log.Warn("Package will not be signed or notarized. Missing the --signAppIdentity and --notaryProfile options.");
|
||||
@@ -76,7 +75,7 @@ public class OsxPackCommandRunner : PackageBuilder<OsxPackOptions>
|
||||
{
|
||||
// create installer package, sign and notarize
|
||||
if (!Options.NoPackage) {
|
||||
var helper = new HelperExe(Log);
|
||||
var helper = new OsxBuildTools(Log);
|
||||
Dictionary<string, string> pkgContent = new() {
|
||||
{"welcome", Options.PackageWelcome },
|
||||
{"license", Options.PackageLicense },
|
||||
@@ -108,7 +107,7 @@ public class OsxPackCommandRunner : PackageBuilder<OsxPackOptions>
|
||||
protected override Task CreatePortablePackage(Action<int> progress, string packDir, string outputPath)
|
||||
{
|
||||
progress(-1); // indeterminate
|
||||
var helper = new HelperExe(Log);
|
||||
var helper = new OsxBuildTools(Log);
|
||||
helper.CreateDittoZip(packDir, outputPath);
|
||||
progress(100);
|
||||
return Task.CompletedTask;
|
||||
|
||||
@@ -8,32 +8,22 @@ using Newtonsoft.Json;
|
||||
|
||||
namespace Velopack.Packaging.Unix;
|
||||
|
||||
public class HelperExe : HelperFile
|
||||
[SupportedOSPlatform("osx")]
|
||||
public class OsxBuildTools
|
||||
{
|
||||
public HelperExe(ILogger logger) : base(logger)
|
||||
public ILogger Log { get; }
|
||||
|
||||
public OsxBuildTools(ILogger logger)
|
||||
{
|
||||
Log = logger;
|
||||
}
|
||||
|
||||
public string VelopackEntitlements => FindHelperFile("Velopack.entitlements");
|
||||
|
||||
protected string AppImageTool => FindHelperFile("appimagetool-x86_64.AppImage");
|
||||
|
||||
[SupportedOSPlatform("linux")]
|
||||
public void CreateLinuxAppImage(string appDir, string outputFile)
|
||||
{
|
||||
var tool = AppImageTool;
|
||||
Chmod.ChmodFileAsExecutable(tool);
|
||||
InvokeAndThrowIfNonZero(tool, new[] { appDir, outputFile }, null);
|
||||
Chmod.ChmodFileAsExecutable(outputFile);
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("osx")]
|
||||
public void CodeSign(string identity, string entitlements, string filePath)
|
||||
{
|
||||
if (String.IsNullOrEmpty(entitlements)) {
|
||||
Log.Info("No entitlements specified, using default: " +
|
||||
"https://docs.microsoft.com/en-us/dotnet/core/install/macos-notarization-issues");
|
||||
entitlements = VelopackEntitlements;
|
||||
entitlements = HelperFile.VelopackEntitlements;
|
||||
}
|
||||
|
||||
if (!File.Exists(entitlements)) {
|
||||
@@ -53,12 +43,11 @@ public class HelperExe : HelperFile
|
||||
|
||||
Log.Info($"Beginning codesign for package...");
|
||||
|
||||
Log.Info(InvokeAndThrowIfNonZero("codesign", args, null));
|
||||
Log.Info(Exe.InvokeAndThrowIfNonZero("codesign", args, null));
|
||||
|
||||
Log.Info("codesign completed successfully");
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("osx")]
|
||||
public void SpctlAssessCode(string filePath)
|
||||
{
|
||||
var args2 = new List<string> {
|
||||
@@ -68,10 +57,9 @@ public class HelperExe : HelperFile
|
||||
};
|
||||
|
||||
Log.Info($"Verifying signature/notarization for code using spctl...");
|
||||
Log.Info(InvokeAndThrowIfNonZero("spctl", args2, null));
|
||||
Log.Info(Exe.InvokeAndThrowIfNonZero("spctl", args2, null));
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("osx")]
|
||||
public void SpctlAssessInstaller(string filePath)
|
||||
{
|
||||
var args2 = new List<string> {
|
||||
@@ -82,10 +70,9 @@ public class HelperExe : HelperFile
|
||||
};
|
||||
|
||||
Log.Info($"Verifying signature/notarization for installer package using spctl...");
|
||||
Log.Info(InvokeAndThrowIfNonZero("spctl", args2, null));
|
||||
Log.Info(Exe.InvokeAndThrowIfNonZero("spctl", args2, null));
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("osx")]
|
||||
public void CreateInstallerPkg(string appBundlePath, string appTitle, string appId, IEnumerable<KeyValuePair<string, string>> extraContent,
|
||||
string pkgOutputPath, string signIdentity, Action<int> progress)
|
||||
{
|
||||
@@ -121,8 +108,8 @@ exit 0
|
||||
|
||||
// generate non-relocatable component pkg. this will be included into a product archive
|
||||
var pkgPlistPath = Path.Combine(tmp, "tmp.plist");
|
||||
InvokeAndThrowIfNonZero("pkgbuild", new[] { "--analyze", "--root", tmpPayload1, pkgPlistPath }, null);
|
||||
InvokeAndThrowIfNonZero("plutil", new[] { "-replace", "BundleIsRelocatable", "-bool", "NO", pkgPlistPath }, null);
|
||||
Exe.InvokeAndThrowIfNonZero("pkgbuild", new[] { "--analyze", "--root", tmpPayload1, pkgPlistPath }, null);
|
||||
Exe.InvokeAndThrowIfNonZero("plutil", new[] { "-replace", "BundleIsRelocatable", "-bool", "NO", pkgPlistPath }, null);
|
||||
progress(50);
|
||||
|
||||
var pkg1Path = Path.Combine(tmpPayload2, "1.pkg");
|
||||
@@ -134,12 +121,12 @@ exit 0
|
||||
pkg1Path,
|
||||
};
|
||||
|
||||
InvokeAndThrowIfNonZero("pkgbuild", args1, null);
|
||||
Exe.InvokeAndThrowIfNonZero("pkgbuild", args1, null);
|
||||
progress(70);
|
||||
|
||||
// create final product package that contains app component
|
||||
var distributionPath = Path.Combine(tmp, "distribution.xml");
|
||||
InvokeAndThrowIfNonZero("productbuild", new[] { "--synthesize", "--package", pkg1Path, distributionPath }, null);
|
||||
Exe.InvokeAndThrowIfNonZero("productbuild", new[] { "--synthesize", "--package", pkg1Path, distributionPath }, null);
|
||||
progress(80);
|
||||
|
||||
// https://developer.apple.com/library/archive/documentation/DeveloperTools/Reference/DistributionDefinitionRef/Chapters/Distribution_XML_Ref.html
|
||||
@@ -175,13 +162,12 @@ exit 0
|
||||
Log.Warn("No Installer signing identity provided. The '.pkg' will not be signed.");
|
||||
}
|
||||
|
||||
InvokeAndThrowIfNonZero("productbuild", args2, null);
|
||||
Exe.InvokeAndThrowIfNonZero("productbuild", args2, null);
|
||||
progress(100);
|
||||
|
||||
Log.Info("Installer created successfully");
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("osx")]
|
||||
public void Notarize(string filePath, string keychainProfileName)
|
||||
{
|
||||
Log.Info($"Preparing to Notarize. This will upload to Apple and usually takes minutes, [underline]but could take hours.[/]");
|
||||
@@ -195,7 +181,7 @@ exit 0
|
||||
filePath
|
||||
};
|
||||
|
||||
var ntresultjson = InvokeProcess("xcrun", args, null);
|
||||
var ntresultjson = Exe.InvokeProcess("xcrun", args, null);
|
||||
Log.Info(ntresultjson.StdOutput);
|
||||
|
||||
// try to catch any notarization errors. if we have a submission id, retrieve notary logs.
|
||||
@@ -210,7 +196,7 @@ exit 0
|
||||
"--keychain-profile", keychainProfileName,
|
||||
};
|
||||
|
||||
var result = InvokeProcess("xcrun", logargs, null);
|
||||
var result = Exe.InvokeProcess("xcrun", logargs, null);
|
||||
Log.Warn(result.StdOutput);
|
||||
}
|
||||
|
||||
@@ -223,11 +209,10 @@ exit 0
|
||||
Log.Info("Notarization completed successfully");
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("osx")]
|
||||
public void Staple(string filePath)
|
||||
{
|
||||
Log.Debug($"Stapling Notarization to '{filePath}'");
|
||||
Log.Info(InvokeAndThrowIfNonZero("xcrun", new[] { "stapler", "staple", filePath }, null));
|
||||
Log.Info(Exe.InvokeAndThrowIfNonZero("xcrun", new[] { "stapler", "staple", filePath }, null));
|
||||
}
|
||||
|
||||
private class NotaryToolResult
|
||||
@@ -237,7 +222,6 @@ exit 0
|
||||
public string status { get; set; }
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("osx")]
|
||||
public void CreateDittoZip(string folder, string outputZip)
|
||||
{
|
||||
if (File.Exists(outputZip)) File.Delete(outputZip);
|
||||
@@ -253,6 +237,6 @@ exit 0
|
||||
};
|
||||
|
||||
Log.Debug($"Creating ditto bundle '{outputZip}'");
|
||||
Log.Debug(InvokeAndThrowIfNonZero("ditto", args, null));
|
||||
Log.Debug(Exe.InvokeAndThrowIfNonZero("ditto", args, null));
|
||||
}
|
||||
}
|
||||
145
src/Velopack.Packaging.Windows/CodeSign.cs
Normal file
145
src/Velopack.Packaging.Windows/CodeSign.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Velopack.Packaging.Windows
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class CodeSign
|
||||
{
|
||||
public ILogger Log { get; }
|
||||
|
||||
public CodeSign(ILogger logger)
|
||||
{
|
||||
Log = logger;
|
||||
}
|
||||
|
||||
private bool CheckIsAlreadySigned(string filePath)
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(filePath)) return true;
|
||||
|
||||
if (!File.Exists(filePath)) {
|
||||
Log.Warn($"Cannot sign '{filePath}', file does not exist.");
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
if (AuthenticodeTools.IsTrusted(filePath)) {
|
||||
Log.Debug($"'{filePath}' is already signed, skipping...");
|
||||
return true;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Log.Error(ex, "Failed to determine signing status for " + filePath);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void SignPEFilesWithSignTool(string rootDir, string[] filePaths, string signArguments, int parallelism, Action<int> progress)
|
||||
{
|
||||
Queue<string> pendingSign = new Queue<string>();
|
||||
|
||||
foreach (var f in filePaths) {
|
||||
if (!CheckIsAlreadySigned(f)) {
|
||||
// try to find the path relative to rootDir
|
||||
if (String.IsNullOrEmpty(rootDir)) {
|
||||
pendingSign.Enqueue(f);
|
||||
} else {
|
||||
var partialPath = Utility.NormalizePath(f).Substring(Utility.NormalizePath(rootDir).Length).Trim('/', '\\');
|
||||
pendingSign.Enqueue(partialPath);
|
||||
}
|
||||
} else {
|
||||
Log.Debug($"'{f}' is already signed, and will not be signed again.");
|
||||
}
|
||||
}
|
||||
|
||||
if (filePaths.Length != pendingSign.Count) {
|
||||
var diff = filePaths.Length - pendingSign.Count;
|
||||
Log.Info($"{pendingSign.Count} files will be signed, {diff} will be skipped because they are already signed.");
|
||||
}
|
||||
|
||||
var totalToSign = pendingSign.Count;
|
||||
var baseSignArgs = CommandLineToArgvW(signArguments);
|
||||
|
||||
do {
|
||||
List<string> args = new List<string>();
|
||||
args.Add("sign");
|
||||
args.AddRange(baseSignArgs);
|
||||
for (int i = Math.Min(pendingSign.Count, parallelism); i > 0; i--) {
|
||||
args.Add(pendingSign.Dequeue());
|
||||
}
|
||||
|
||||
var result = Exe.InvokeProcess(HelperFile.SignToolPath, args, rootDir);
|
||||
if (result.ExitCode != 0) {
|
||||
var cmdWithPasswordHidden = new Regex(@"\/p\s+?[^\s]+").Replace(result.Command, "/p ********");
|
||||
Log.Debug($"Signing command failed: {cmdWithPasswordHidden}");
|
||||
throw new Exception(
|
||||
$"Signing command failed. Specify --verbose argument to print signing command.\n\n" +
|
||||
$"Output was:\n" + result.StdOutput);
|
||||
}
|
||||
|
||||
int processed = totalToSign - pendingSign.Count;
|
||||
Log.Debug($"Signed {processed}/{totalToSign} successfully.\r\n" + result.StdOutput);
|
||||
progress((int) ((double) processed / totalToSign * 100));
|
||||
} while (pendingSign.Count > 0);
|
||||
}
|
||||
|
||||
public void SignPEFileWithTemplate(string filePath, string signTemplate)
|
||||
{
|
||||
if (VelopackRuntimeInfo.IsWindows && CheckIsAlreadySigned(filePath)) {
|
||||
Log.Debug($"'{filePath}' is already signed, and will not be signed again.");
|
||||
return;
|
||||
}
|
||||
|
||||
var command = signTemplate.Replace("\"{{file}}\"", "{{file}}").Replace("{{file}}", $"\"{filePath}\"");
|
||||
|
||||
var result = Exe.InvokeProcess(command, null, null);
|
||||
if (result.ExitCode != 0) {
|
||||
var cmdWithPasswordHidden = new Regex(@"\/p\s+?[^\s]+").Replace(result.Command, "/p ********");
|
||||
Log.Debug($"Signing command failed: {cmdWithPasswordHidden}");
|
||||
throw new Exception(
|
||||
$"Signing command failed. Specify --verbose argument to print signing command.\n\n" +
|
||||
$"Output was:\n" + result.StdOutput);
|
||||
}
|
||||
|
||||
Log.Info("Sign successful: " + result.StdOutput);
|
||||
}
|
||||
|
||||
private const string WIN_KERNEL32 = "kernel32.dll";
|
||||
private const string WIN_SHELL32 = "shell32.dll";
|
||||
|
||||
[DllImport(WIN_KERNEL32, EntryPoint = "LocalFree", SetLastError = true)]
|
||||
private static extern IntPtr _LocalFree(IntPtr hMem);
|
||||
|
||||
[DllImport(WIN_SHELL32, EntryPoint = "CommandLineToArgvW", CharSet = CharSet.Unicode)]
|
||||
private static extern IntPtr _CommandLineToArgvW([MarshalAs(UnmanagedType.LPWStr)] string cmdLine, out int numArgs);
|
||||
|
||||
protected static string[] CommandLineToArgvW(string cmdLine)
|
||||
{
|
||||
IntPtr argv = IntPtr.Zero;
|
||||
try {
|
||||
argv = _CommandLineToArgvW(cmdLine, out var numArgs);
|
||||
if (argv == IntPtr.Zero) {
|
||||
throw new Win32Exception();
|
||||
}
|
||||
var result = new string[numArgs];
|
||||
|
||||
for (int i = 0; i < numArgs; i++) {
|
||||
IntPtr currArg = Marshal.ReadIntPtr(argv, i * Marshal.SizeOf(typeof(IntPtr)));
|
||||
result[i] = Marshal.PtrToStringUni(currArg);
|
||||
}
|
||||
|
||||
return result;
|
||||
} finally {
|
||||
_LocalFree(argv);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading.Channels;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NuGet.Versioning;
|
||||
@@ -9,6 +10,7 @@ using Velopack.Windows;
|
||||
|
||||
namespace Velopack.Packaging.Windows.Commands;
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class WindowsPackCommandRunner : PackageBuilder<WindowsPackOptions>
|
||||
{
|
||||
public WindowsPackCommandRunner(ILogger logger)
|
||||
@@ -42,13 +44,12 @@ public class WindowsPackCommandRunner : PackageBuilder<WindowsPackOptions>
|
||||
File.WriteAllText(Path.Combine(dir.FullName, "sq.version"), nuspecText);
|
||||
packDir = dir.FullName;
|
||||
|
||||
var helper = new HelperExe(Log);
|
||||
var updatePath = Path.Combine(TempDir.FullName, "Update.exe");
|
||||
File.Copy(helper.UpdatePath, updatePath, true);
|
||||
File.Copy(HelperFile.GetUpdatePath(), updatePath, true);
|
||||
|
||||
// update icon for Update.exe if requested
|
||||
if (Options.Icon != null && VelopackRuntimeInfo.IsWindows) {
|
||||
helper.SetExeIcon(updatePath, Options.Icon);
|
||||
Rcedit.SetExeIcon(updatePath, Options.Icon);
|
||||
} else if (Options.Icon != null) {
|
||||
Log.Warn("Unable to set icon for Update.exe (only supported on windows).");
|
||||
}
|
||||
@@ -80,12 +81,11 @@ public class WindowsPackCommandRunner : PackageBuilder<WindowsPackOptions>
|
||||
|
||||
protected override Task CreateSetupPackage(Action<int> progress, string releasePkg, string packDir, string targetSetupExe)
|
||||
{
|
||||
var helper = new HelperExe(Log);
|
||||
var bundledzp = new ZipPackage(releasePkg);
|
||||
File.Copy(helper.SetupPath, targetSetupExe, true);
|
||||
Utility.Retry(() => File.Copy(HelperFile.SetupPath, targetSetupExe, true));
|
||||
progress(10);
|
||||
if (VelopackRuntimeInfo.IsWindows) {
|
||||
helper.SetPEVersionBlockFromPackageInfo(targetSetupExe, bundledzp, Options.Icon);
|
||||
Rcedit.SetPEVersionBlockFromPackageInfo(targetSetupExe, bundledzp, Options.Icon);
|
||||
} else {
|
||||
Log.Warn("Unable to set Setup.exe icon (only supported on windows)");
|
||||
}
|
||||
@@ -129,9 +129,7 @@ public class WindowsPackCommandRunner : PackageBuilder<WindowsPackOptions>
|
||||
{
|
||||
try {
|
||||
var target = Path.Combine(targetDir, Path.GetFileName(exeToCopy));
|
||||
var helper = new HelperExe(Log);
|
||||
|
||||
Utility.Retry(() => File.Copy(helper.StubExecutablePath, target, true));
|
||||
Utility.Retry(() => File.Copy(HelperFile.StubExecutablePath, target, true));
|
||||
Utility.Retry(() => {
|
||||
if (VelopackRuntimeInfo.IsWindows) {
|
||||
using var writer = new Microsoft.NET.HostModel.ResourceUpdater(target, true);
|
||||
@@ -151,7 +149,7 @@ public class WindowsPackCommandRunner : PackageBuilder<WindowsPackOptions>
|
||||
var signParams = options.SignParameters;
|
||||
var signTemplate = options.SignTemplate;
|
||||
var signParallel = options.SignParallel;
|
||||
var helper = new HelperExe(Log);
|
||||
var helper = new CodeSign(Log);
|
||||
|
||||
if (string.IsNullOrEmpty(signParams) && string.IsNullOrEmpty(signTemplate)) {
|
||||
Log.Warn($"No signing paramaters provided, {filePaths.Length} file(s) will not be signed.");
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Velopack.Packaging.Windows;
|
||||
|
||||
public class HelperExe : HelperFile
|
||||
{
|
||||
public HelperExe(ILogger logger) : base(logger)
|
||||
{
|
||||
}
|
||||
|
||||
public string SetupPath => FindHelperFile("Setup.exe");
|
||||
|
||||
public string StubExecutablePath => FindHelperFile("stub.exe");
|
||||
|
||||
private string SignToolPath => FindHelperFile("signtool.exe");
|
||||
|
||||
private string RceditPath => FindHelperFile("rcedit.exe");
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
private bool CheckIsAlreadySigned(string filePath)
|
||||
{
|
||||
if (String.IsNullOrWhiteSpace(filePath)) return true;
|
||||
|
||||
if (!File.Exists(filePath)) {
|
||||
Log.Warn($"Cannot sign '{filePath}', file does not exist.");
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
if (AuthenticodeTools.IsTrusted(filePath)) {
|
||||
Log.Debug($"'{filePath}' is already signed, skipping...");
|
||||
return true;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Log.Error(ex, "Failed to determine signing status for " + filePath);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
public void SignPEFilesWithSignTool(string rootDir, string[] filePaths, string signArguments, int parallelism, Action<int> progress)
|
||||
{
|
||||
Queue<string> pendingSign = new Queue<string>();
|
||||
|
||||
foreach (var f in filePaths) {
|
||||
if (!CheckIsAlreadySigned(f)) {
|
||||
// try to find the path relative to rootDir
|
||||
if (String.IsNullOrEmpty(rootDir)) {
|
||||
pendingSign.Enqueue(f);
|
||||
} else {
|
||||
var partialPath = Utility.NormalizePath(f).Substring(Utility.NormalizePath(rootDir).Length).Trim('/', '\\');
|
||||
pendingSign.Enqueue(partialPath);
|
||||
}
|
||||
} else {
|
||||
Log.Debug($"'{f}' is already signed, and will not be signed again.");
|
||||
}
|
||||
}
|
||||
|
||||
if (filePaths.Length != pendingSign.Count) {
|
||||
var diff = filePaths.Length - pendingSign.Count;
|
||||
Log.Info($"{pendingSign.Count} files will be signed, {diff} will be skipped because they are already signed.");
|
||||
}
|
||||
|
||||
var totalToSign = pendingSign.Count;
|
||||
var baseSignArgs = CommandLineToArgvW(signArguments);
|
||||
|
||||
do {
|
||||
List<string> args = new List<string>();
|
||||
args.Add("sign");
|
||||
args.AddRange(baseSignArgs);
|
||||
for (int i = Math.Min(pendingSign.Count, parallelism); i > 0; i--) {
|
||||
args.Add(pendingSign.Dequeue());
|
||||
}
|
||||
|
||||
var result = InvokeProcess(SignToolPath, args, rootDir);
|
||||
if (result.ExitCode != 0) {
|
||||
var cmdWithPasswordHidden = new Regex(@"\/p\s+?[^\s]+").Replace(result.Command, "/p ********");
|
||||
Log.Debug($"Signing command failed: {cmdWithPasswordHidden}");
|
||||
throw new Exception(
|
||||
$"Signing command failed. Specify --verbose argument to print signing command.\n\n" +
|
||||
$"Output was:\n" + result.StdOutput);
|
||||
}
|
||||
|
||||
int processed = totalToSign - pendingSign.Count;
|
||||
Log.Debug($"Signed {processed}/{totalToSign} successfully.\r\n" + result.StdOutput);
|
||||
progress((int) ((double) processed / totalToSign * 100));
|
||||
} while (pendingSign.Count > 0);
|
||||
}
|
||||
|
||||
public void SignPEFileWithTemplate(string filePath, string signTemplate)
|
||||
{
|
||||
if (VelopackRuntimeInfo.IsWindows && CheckIsAlreadySigned(filePath)) {
|
||||
Log.Debug($"'{filePath}' is already signed, and will not be signed again.");
|
||||
return;
|
||||
}
|
||||
|
||||
var command = signTemplate.Replace("\"{{file}}\"", "{{file}}").Replace("{{file}}", $"\"{filePath}\"");
|
||||
|
||||
var result = InvokeProcess(command, null, null);
|
||||
if (result.ExitCode != 0) {
|
||||
var cmdWithPasswordHidden = new Regex(@"\/p\s+?[^\s]+").Replace(result.Command, "/p ********");
|
||||
Log.Debug($"Signing command failed: {cmdWithPasswordHidden}");
|
||||
throw new Exception(
|
||||
$"Signing command failed. Specify --verbose argument to print signing command.\n\n" +
|
||||
$"Output was:\n" + result.StdOutput);
|
||||
}
|
||||
|
||||
Log.Info("Sign successful: " + result.StdOutput);
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
public void SetExeIcon(string exePath, string iconPath)
|
||||
{
|
||||
Log.Debug("Updating PE icon for: " + exePath);
|
||||
var args = new[] { Path.GetFullPath(exePath), "--set-icon", iconPath };
|
||||
Utility.Retry(() => InvokeAndThrowIfNonZero(RceditPath, args, null));
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
public void SetPEVersionBlockFromPackageInfo(string exePath, NuGet.IPackage package, string iconPath = null)
|
||||
{
|
||||
Log.Debug("Updating StringTable resources for: " + exePath);
|
||||
var realExePath = Path.GetFullPath(exePath);
|
||||
|
||||
List<string> args = new List<string>() {
|
||||
realExePath,
|
||||
"--set-version-string", "CompanyName", package.ProductCompany,
|
||||
"--set-version-string", "LegalCopyright", package.ProductCopyright,
|
||||
"--set-version-string", "FileDescription", package.ProductDescription,
|
||||
"--set-version-string", "ProductName", package.ProductName,
|
||||
"--set-file-version", package.Version.ToString(),
|
||||
"--set-product-version", package.Version.ToString(),
|
||||
};
|
||||
|
||||
if (iconPath != null) {
|
||||
args.Add("--set-icon");
|
||||
args.Add(Path.GetFullPath(iconPath));
|
||||
}
|
||||
|
||||
Utility.Retry(() => InvokeAndThrowIfNonZero(RceditPath, args, null));
|
||||
}
|
||||
|
||||
private const string WIN_KERNEL32 = "kernel32.dll";
|
||||
private const string WIN_SHELL32 = "shell32.dll";
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
[DllImport(WIN_KERNEL32, EntryPoint = "LocalFree", SetLastError = true)]
|
||||
private static extern IntPtr _LocalFree(IntPtr hMem);
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
[DllImport(WIN_SHELL32, EntryPoint = "CommandLineToArgvW", CharSet = CharSet.Unicode)]
|
||||
private static extern IntPtr _CommandLineToArgvW([MarshalAs(UnmanagedType.LPWStr)] string cmdLine, out int numArgs);
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
protected static string[] CommandLineToArgvW(string cmdLine)
|
||||
{
|
||||
IntPtr argv = IntPtr.Zero;
|
||||
try {
|
||||
argv = _CommandLineToArgvW(cmdLine, out var numArgs);
|
||||
if (argv == IntPtr.Zero) {
|
||||
throw new Win32Exception();
|
||||
}
|
||||
var result = new string[numArgs];
|
||||
|
||||
for (int i = 0; i < numArgs; i++) {
|
||||
IntPtr currArg = Marshal.ReadIntPtr(argv, i * Marshal.SizeOf(typeof(IntPtr)));
|
||||
result[i] = Marshal.PtrToStringUni(currArg);
|
||||
}
|
||||
|
||||
return result;
|
||||
} finally {
|
||||
_LocalFree(argv);
|
||||
}
|
||||
}
|
||||
}
|
||||
42
src/Velopack.Packaging.Windows/Rcedit.cs
Normal file
42
src/Velopack.Packaging.Windows/Rcedit.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Velopack.Packaging.Windows
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public class Rcedit
|
||||
{
|
||||
public static void SetExeIcon(string exePath, string iconPath)
|
||||
{
|
||||
var args = new[] { Path.GetFullPath(exePath), "--set-icon", iconPath };
|
||||
Utility.Retry(() => Exe.InvokeAndThrowIfNonZero(HelperFile.RceditPath, args, null));
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
public static void SetPEVersionBlockFromPackageInfo(string exePath, NuGet.IPackage package, string iconPath = null)
|
||||
{
|
||||
var realExePath = Path.GetFullPath(exePath);
|
||||
|
||||
List<string> args = new List<string>() {
|
||||
realExePath,
|
||||
"--set-version-string", "CompanyName", package.ProductCompany,
|
||||
"--set-version-string", "LegalCopyright", package.ProductCopyright,
|
||||
"--set-version-string", "FileDescription", package.ProductDescription,
|
||||
"--set-version-string", "ProductName", package.ProductName,
|
||||
"--set-file-version", package.Version.ToString(),
|
||||
"--set-product-version", package.Version.ToString(),
|
||||
};
|
||||
|
||||
if (iconPath != null) {
|
||||
args.Add("--set-icon");
|
||||
args.Add(Path.GetFullPath(iconPath));
|
||||
}
|
||||
|
||||
Utility.Retry(() => Exe.InvokeAndThrowIfNonZero(HelperFile.RceditPath, args, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Velopack.Compression;
|
||||
using Velopack.Packaging.Exceptions;
|
||||
|
||||
namespace Velopack.Packaging.Commands
|
||||
{
|
||||
@@ -26,10 +27,8 @@ namespace Velopack.Packaging.Commands
|
||||
|
||||
var tmp = Utility.GetDefaultTempBaseDirectory();
|
||||
using var _1 = Utility.GetTempDirectory(out var workDir);
|
||||
var helper = new HelperFile(_logger);
|
||||
|
||||
var updateExe = helper.GetUpdatePath();
|
||||
var delta = new DeltaPackage(_logger, tmp, updateExe);
|
||||
var delta = new DeltaEmbedded(HelperFile.GetZstdPath(), _logger, tmp);
|
||||
EasyZip.ExtractZipToDirectory(_logger, options.BasePackage, workDir);
|
||||
|
||||
await Progress.ExecuteAsync(_logger, async (ctx) => {
|
||||
|
||||
35
src/Velopack.Packaging/DeltaEmbedded.cs
Normal file
35
src/Velopack.Packaging/DeltaEmbedded.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Velopack.Compression;
|
||||
|
||||
namespace Velopack.Packaging
|
||||
{
|
||||
public class DeltaEmbedded
|
||||
{
|
||||
private readonly DeltaImpl _delta;
|
||||
|
||||
public DeltaEmbedded(string zstdPath, ILogger logger, string baseTmpDir)
|
||||
{
|
||||
_delta = new DeltaImpl(zstdPath, logger, baseTmpDir);
|
||||
}
|
||||
|
||||
public void ApplyDeltaPackageFast(string workingPath, string deltaPackageZip, Action<int> progress = null)
|
||||
{
|
||||
_delta.ApplyDeltaPackageFast(workingPath, deltaPackageZip, progress);
|
||||
}
|
||||
|
||||
private class DeltaImpl : DeltaPackage
|
||||
{
|
||||
private readonly Zstd _zstd;
|
||||
|
||||
public DeltaImpl(string zstdPath, ILogger logger, string baseTmpDir) : base(logger, baseTmpDir)
|
||||
{
|
||||
_zstd = new Zstd(zstdPath);
|
||||
}
|
||||
|
||||
protected override void ApplyZstdPatch(string baseFile, string patchFile, string outputFile)
|
||||
{
|
||||
_zstd.ApplyPatch(baseFile, patchFile, outputFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
src/Velopack.Packaging/DeltaMode.cs
Normal file
8
src/Velopack.Packaging/DeltaMode.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Velopack.Packaging;
|
||||
|
||||
public enum DeltaMode
|
||||
{
|
||||
None,
|
||||
BestSpeed,
|
||||
BestSize,
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using System.Runtime.Intrinsics.Arm;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Velopack.Compression;
|
||||
using Velopack.Packaging.Exceptions;
|
||||
|
||||
namespace Velopack.Packaging;
|
||||
|
||||
@@ -32,16 +33,12 @@ public class DeltaPackageBuilder
|
||||
if (newPackage == null) throw new ArgumentNullException(nameof(newPackage));
|
||||
if (String.IsNullOrEmpty(outputFile) || File.Exists(outputFile)) throw new ArgumentException("The output file is null or already exists", nameof(outputFile));
|
||||
|
||||
bool isZstdAvailable = true;
|
||||
var helper = new HelperFile(_logger);
|
||||
if (!VelopackRuntimeInfo.IsWindows) {
|
||||
try {
|
||||
helper.AssertSystemBinaryExists("zstd");
|
||||
} catch (Exception ex) {
|
||||
_logger.Error(ex.Message);
|
||||
_logger.Warn("Falling back to legacy bsdiff delta format. This will be a lot slower and more prone to breaking.");
|
||||
isZstdAvailable = false;
|
||||
}
|
||||
Zstd zstd = null;
|
||||
try {
|
||||
zstd = new Zstd(HelperFile.GetZstdPath());
|
||||
} catch (Exception ex) {
|
||||
_logger.Error(ex.Message);
|
||||
_logger.Warn("Zstd not available. Falling back to legacy bsdiff delta format. This will be a lot slower and more prone to breaking.");
|
||||
}
|
||||
|
||||
if (basePackage.Version >= newPackage.Version) {
|
||||
@@ -125,7 +122,7 @@ public class DeltaPackageBuilder
|
||||
// 3. changed, write a delta in new
|
||||
if (useZstd) {
|
||||
var diffOut = targetFile.FullName + ".zsdiff";
|
||||
helper.CreateZstdPatch(oldFilePath, targetFile.FullName, diffOut, mode);
|
||||
zstd.CreatePatch(oldFilePath, targetFile.FullName, diffOut, mode);
|
||||
} else {
|
||||
var oldData = File.ReadAllBytes(oldFilePath);
|
||||
var newData = File.ReadAllBytes(targetFile.FullName);
|
||||
@@ -156,7 +153,7 @@ public class DeltaPackageBuilder
|
||||
try {
|
||||
Parallel.ForEach(newLibFiles, new ParallelOptions() { MaxDegreeOfParallelism = numParallel }, (f) => {
|
||||
// we try to use zstd first, if it fails we'll try bsdiff
|
||||
if (isZstdAvailable) {
|
||||
if (zstd != null) {
|
||||
try {
|
||||
createDeltaForSingleFile(f, tempInfo, true);
|
||||
return; // success, so return from this function
|
||||
@@ -167,7 +164,7 @@ public class DeltaPackageBuilder
|
||||
// if we're here, either zstd is not available or it failed
|
||||
try {
|
||||
createDeltaForSingleFile(f, tempInfo, false);
|
||||
if (isZstdAvailable) {
|
||||
if (zstd != null) {
|
||||
_logger.Info($"Successfully created fallback bsdiff for file '{f.FullName}'.");
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Velopack.Packaging;
|
||||
namespace Velopack.Packaging.Exceptions;
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class ProcessFailedException : Exception
|
||||
@@ -6,7 +6,7 @@ using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Velopack.Packaging
|
||||
namespace Velopack.Packaging.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Denotes that an error has occurred for which a stack trace should not be printed.
|
||||
@@ -5,7 +5,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Velopack.Packaging
|
||||
namespace Velopack.Packaging.Exceptions
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class VelopackAppVerificationException : UserInfoException
|
||||
79
src/Velopack.Packaging/Exe.cs
Normal file
79
src/Velopack.Packaging/Exe.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Velopack.Packaging.Exceptions;
|
||||
|
||||
namespace Velopack.Packaging
|
||||
{
|
||||
public static class Exe
|
||||
{
|
||||
public static void AssertSystemBinaryExists(string binaryName)
|
||||
{
|
||||
try {
|
||||
if (VelopackRuntimeInfo.IsWindows) {
|
||||
var output = InvokeAndThrowIfNonZero("where", new[] { binaryName }, null);
|
||||
if (String.IsNullOrWhiteSpace(output) || !File.Exists(output))
|
||||
throw new ProcessFailedException("", "");
|
||||
} else if (VelopackRuntimeInfo.IsOSX) {
|
||||
InvokeAndThrowIfNonZero("command", new[] { "-v", binaryName }, null);
|
||||
} else if (VelopackRuntimeInfo.IsLinux) {
|
||||
InvokeAndThrowIfNonZero("which", new[] { binaryName }, null);
|
||||
} else {
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
} catch (ProcessFailedException) {
|
||||
throw new Exception($"Could not find '{binaryName}' on the system, ensure it is installed and on the PATH.");
|
||||
}
|
||||
}
|
||||
|
||||
public static string InvokeAndThrowIfNonZero(string exePath, IEnumerable<string> args, string workingDir)
|
||||
{
|
||||
var result = InvokeProcess(exePath, args, workingDir);
|
||||
ProcessFailedException.ThrowIfNonZero(result);
|
||||
return result.StdOutput;
|
||||
}
|
||||
|
||||
public static (int ExitCode, string StdOutput) InvokeProcess(ProcessStartInfo psi, CancellationToken ct)
|
||||
{
|
||||
var pi = Process.Start(psi);
|
||||
while (!ct.IsCancellationRequested) {
|
||||
if (pi.WaitForExit(500)) break;
|
||||
}
|
||||
|
||||
if (ct.IsCancellationRequested && !pi.HasExited) {
|
||||
pi.Kill();
|
||||
ct.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
string output = pi.StandardOutput.ReadToEnd();
|
||||
string error = pi.StandardError.ReadToEnd();
|
||||
var all = (output ?? "") + Environment.NewLine + (error ?? "");
|
||||
|
||||
return (pi.ExitCode, all.Trim());
|
||||
}
|
||||
|
||||
public static (int ExitCode, string StdOutput, string Command) InvokeProcess(string fileName, IEnumerable<string> args, string workingDirectory, CancellationToken ct = default)
|
||||
{
|
||||
var psi = CreateProcessStartInfo(fileName, workingDirectory);
|
||||
psi.AppendArgumentListSafe(args, out var argString);
|
||||
var p = InvokeProcess(psi, ct);
|
||||
return (p.ExitCode, p.StdOutput, $"{fileName} {argString}");
|
||||
}
|
||||
|
||||
public static ProcessStartInfo CreateProcessStartInfo(string fileName, string workingDirectory)
|
||||
{
|
||||
var psi = new ProcessStartInfo(fileName);
|
||||
psi.UseShellExecute = false;
|
||||
psi.WindowStyle = ProcessWindowStyle.Hidden;
|
||||
psi.ErrorDialog = false;
|
||||
psi.CreateNoWindow = true;
|
||||
psi.RedirectStandardOutput = true;
|
||||
psi.RedirectStandardError = true;
|
||||
psi.WorkingDirectory = workingDirectory ?? Environment.CurrentDirectory;
|
||||
return psi;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,63 +1,72 @@
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Velopack.Packaging;
|
||||
|
||||
public enum DeltaMode
|
||||
public static class HelperFile
|
||||
{
|
||||
None,
|
||||
BestSpeed,
|
||||
BestSize,
|
||||
}
|
||||
|
||||
public class HelperFile
|
||||
{
|
||||
public string UpdatePath => FindHelperFile("Update.exe");
|
||||
|
||||
#if DEBUG
|
||||
public string UpdateMacPath => FindHelperFile("update", MachO.IsMachOImage);
|
||||
public string UpdateNixPath => FindHelperFile("update");
|
||||
|
||||
#else
|
||||
public string UpdateMacPath => FindHelperFile("UpdateMac", MachO.IsMachOImage);
|
||||
public string UpdateNixPath => FindHelperFile("UpdateNix");
|
||||
#endif
|
||||
|
||||
public string GetUpdatePath(RuntimeOs? os = null)
|
||||
public static string GetUpdateExeName(RuntimeOs? os = null)
|
||||
{
|
||||
var _os = os ?? VelopackRuntimeInfo.SystemOs;
|
||||
|
||||
switch (_os) {
|
||||
case RuntimeOs.Windows:
|
||||
return UpdatePath;
|
||||
return FindHelperFile("Update.exe");
|
||||
#if DEBUG
|
||||
case RuntimeOs.Linux:
|
||||
return UpdateNixPath;
|
||||
return FindHelperFile("update");
|
||||
case RuntimeOs.OSX:
|
||||
return UpdateMacPath;
|
||||
return FindHelperFile("update");
|
||||
#else
|
||||
case RuntimeOs.Linux:
|
||||
return FindHelperFile("UpdateNix");
|
||||
case RuntimeOs.OSX:
|
||||
return FindHelperFile("UpdateMac");
|
||||
#endif
|
||||
default:
|
||||
throw new PlatformNotSupportedException("Update binary is not available for this platform.");
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetUpdatePath(RuntimeOs? os = null) => FindHelperFile(GetUpdateExeName(os));
|
||||
|
||||
public static string GetZstdPath()
|
||||
{
|
||||
if (VelopackRuntimeInfo.IsWindows)
|
||||
return FindHelperFile("zstd.exe");
|
||||
Exe.AssertSystemBinaryExists("zstd");
|
||||
return "zstd";
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("macos")]
|
||||
public static string VelopackEntitlements => FindHelperFile("Velopack.entitlements");
|
||||
|
||||
[SupportedOSPlatform("linux")]
|
||||
public static string AppImageToolX64 => FindHelperFile("appimagetool-x86_64.AppImage");
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
public static string SetupPath => FindHelperFile("Setup.exe");
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
public static string StubExecutablePath => FindHelperFile("stub.exe");
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
public static string SignToolPath => FindHelperFile("signtool.exe");
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
public static string RceditPath => FindHelperFile("rcedit.exe");
|
||||
|
||||
private static List<string> _searchPaths = new List<string>();
|
||||
protected readonly ILogger Log;
|
||||
|
||||
static HelperFile()
|
||||
{
|
||||
#if !DEBUG
|
||||
#if DEBUG
|
||||
AddSearchPath(AppContext.BaseDirectory);
|
||||
AddSearchPath(AppContext.BaseDirectory, "..", "..", "..", "src", "Rust", "target", "debug");
|
||||
#else
|
||||
AddSearchPath(AppContext.BaseDirectory, "..", "..", "..", "vendor");
|
||||
#endif
|
||||
}
|
||||
|
||||
public HelperFile(ILogger logger)
|
||||
{
|
||||
Log = logger;
|
||||
}
|
||||
|
||||
public static string FindTestFile(string toFind) => FindHelperFile(toFind, throwWhenNotFound: true);
|
||||
|
||||
public static void AddSearchPath(params string[] pathParts)
|
||||
{
|
||||
AddSearchPath(Path.Combine(pathParts));
|
||||
@@ -69,89 +78,15 @@ public class HelperFile
|
||||
_searchPaths.Insert(0, path);
|
||||
}
|
||||
|
||||
public void CreateZstdPatch(string oldFile, string newFile, string outputFile, DeltaMode mode)
|
||||
public static string FindHelperFile(string toFind, Func<string, bool> predicate = null, bool throwWhenNotFound = true)
|
||||
{
|
||||
if (mode == DeltaMode.None)
|
||||
throw new ArgumentException("DeltaMode.None is not supported.", nameof(mode));
|
||||
|
||||
List<string> args = new() {
|
||||
"--patch-from", oldFile,
|
||||
newFile,
|
||||
"-o", outputFile,
|
||||
"--force",
|
||||
};
|
||||
|
||||
if (mode == DeltaMode.BestSize) {
|
||||
args.Add("-19");
|
||||
args.Add("--single-thread");
|
||||
args.Add("--zstd");
|
||||
args.Add("targetLength=4096");
|
||||
args.Add("--zstd");
|
||||
args.Add("chainLog=30");
|
||||
}
|
||||
|
||||
string zstdPath;
|
||||
if (VelopackRuntimeInfo.IsWindows) {
|
||||
zstdPath = FindHelperFile("zstd.exe");
|
||||
} else {
|
||||
zstdPath = "zstd";
|
||||
AssertSystemBinaryExists(zstdPath);
|
||||
}
|
||||
|
||||
InvokeAndThrowIfNonZero(zstdPath, args, null);
|
||||
}
|
||||
|
||||
public void AssertSystemBinaryExists(string binaryName)
|
||||
{
|
||||
try {
|
||||
if (VelopackRuntimeInfo.IsWindows) {
|
||||
var output = InvokeAndThrowIfNonZero("where", new[] { binaryName }, null);
|
||||
if (String.IsNullOrWhiteSpace(output) || !File.Exists(output))
|
||||
throw new ProcessFailedException("", "");
|
||||
} else if (VelopackRuntimeInfo.IsOSX) {
|
||||
InvokeAndThrowIfNonZero("command", new[] { "-v", binaryName }, null);
|
||||
} else if (VelopackRuntimeInfo.IsLinux) {
|
||||
InvokeAndThrowIfNonZero("which", new[] { binaryName }, null);
|
||||
} else {
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
} catch (ProcessFailedException) {
|
||||
throw new Exception($"Could not find '{binaryName}' on the system, ensure it is installed and on the PATH.");
|
||||
}
|
||||
}
|
||||
|
||||
// protected static string FindAny(params string[] names)
|
||||
// {
|
||||
// var findCommand = SquirrelRuntimeInfo.IsWindows ? "where" : "which";
|
||||
//
|
||||
// // first search the usual places
|
||||
// foreach (var n in names) {
|
||||
// var helper = FindHelperFile(n, throwWhenNotFound: false);
|
||||
// if (helper != null)
|
||||
// return helper;
|
||||
// }
|
||||
//
|
||||
// // then see if there is something on the path
|
||||
// foreach (var n in names) {
|
||||
// var result = ProcessUtil.InvokeProcess(findCommand, new[] { n }, null, CancellationToken.None);
|
||||
// if (result.ExitCode == 0) {
|
||||
// return n;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// throw new Exception($"Could not find any of {String.Join(", ", names)}.");
|
||||
// }
|
||||
|
||||
protected static string FindHelperFile(string toFind, Func<string, bool> predicate = null, bool throwWhenNotFound = true)
|
||||
{
|
||||
var baseDirs = new[] {
|
||||
AppContext.BaseDirectory,
|
||||
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location),
|
||||
Environment.CurrentDirectory,
|
||||
};
|
||||
//var baseDirs = new[] {
|
||||
// AppContext.BaseDirectory,
|
||||
// Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location),
|
||||
// Environment.CurrentDirectory,
|
||||
//};
|
||||
|
||||
var files = _searchPaths
|
||||
.Concat(baseDirs)
|
||||
.Where(d => !String.IsNullOrEmpty(d))
|
||||
.Distinct()
|
||||
.Select(d => Path.Combine(d, toFind))
|
||||
@@ -167,51 +102,4 @@ public class HelperFile
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected static string InvokeAndThrowIfNonZero(string exePath, IEnumerable<string> args, string workingDir)
|
||||
{
|
||||
var result = InvokeProcess(exePath, args, workingDir);
|
||||
ProcessFailedException.ThrowIfNonZero(result);
|
||||
return result.StdOutput;
|
||||
}
|
||||
|
||||
protected static (int ExitCode, string StdOutput) InvokeProcess(ProcessStartInfo psi, CancellationToken ct)
|
||||
{
|
||||
var pi = Process.Start(psi);
|
||||
while (!ct.IsCancellationRequested) {
|
||||
if (pi.WaitForExit(500)) break;
|
||||
}
|
||||
|
||||
if (ct.IsCancellationRequested && !pi.HasExited) {
|
||||
pi.Kill();
|
||||
ct.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
string output = pi.StandardOutput.ReadToEnd();
|
||||
string error = pi.StandardError.ReadToEnd();
|
||||
var all = (output ?? "") + Environment.NewLine + (error ?? "");
|
||||
|
||||
return (pi.ExitCode, all.Trim());
|
||||
}
|
||||
|
||||
protected static (int ExitCode, string StdOutput, string Command) InvokeProcess(string fileName, IEnumerable<string> args, string workingDirectory, CancellationToken ct = default)
|
||||
{
|
||||
var psi = CreateProcessStartInfo(fileName, workingDirectory);
|
||||
psi.AppendArgumentListSafe(args, out var argString);
|
||||
var p = InvokeProcess(psi, ct);
|
||||
return (p.ExitCode, p.StdOutput, $"{fileName} {argString}");
|
||||
}
|
||||
|
||||
protected static ProcessStartInfo CreateProcessStartInfo(string fileName, string workingDirectory)
|
||||
{
|
||||
var psi = new ProcessStartInfo(fileName);
|
||||
psi.UseShellExecute = false;
|
||||
psi.WindowStyle = ProcessWindowStyle.Hidden;
|
||||
psi.ErrorDialog = false;
|
||||
psi.CreateNoWindow = true;
|
||||
psi.RedirectStandardOutput = true;
|
||||
psi.RedirectStandardError = true;
|
||||
psi.WorkingDirectory = workingDirectory ?? Environment.CurrentDirectory;
|
||||
return psi;
|
||||
}
|
||||
}
|
||||
|
||||
11
src/Velopack.Packaging/IPackOptions.cs
Normal file
11
src/Velopack.Packaging/IPackOptions.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace Velopack.Packaging
|
||||
{
|
||||
public interface IPackOptions : INugetPackCommand
|
||||
{
|
||||
RID TargetRuntime { get; }
|
||||
DirectoryInfo ReleaseDir { get; }
|
||||
string Channel { get; }
|
||||
DeltaMode DeltaMode { get; }
|
||||
string EntryExecutableName { get; }
|
||||
}
|
||||
}
|
||||
@@ -6,18 +6,10 @@ using Microsoft.Extensions.Logging;
|
||||
using NuGet.Versioning;
|
||||
using Spectre.Console;
|
||||
using Velopack.Compression;
|
||||
using Velopack.Packaging.Exceptions;
|
||||
|
||||
namespace Velopack.Packaging
|
||||
{
|
||||
public interface IPackOptions : INugetPackCommand
|
||||
{
|
||||
RID TargetRuntime { get; }
|
||||
DirectoryInfo ReleaseDir { get; }
|
||||
string Channel { get; }
|
||||
DeltaMode DeltaMode { get; }
|
||||
string EntryExecutableName { get; }
|
||||
}
|
||||
|
||||
public abstract class PackageBuilder<T> : ICommand<T>
|
||||
where T : class, IPackOptions
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NuGet.Versioning;
|
||||
using Velopack.Packaging.Exceptions;
|
||||
using Velopack.Sources;
|
||||
|
||||
namespace Velopack.Packaging
|
||||
|
||||
70
src/Velopack.Packaging/Zstd.cs
Normal file
70
src/Velopack.Packaging/Zstd.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using Velopack.Packaging.Exceptions;
|
||||
|
||||
namespace Velopack.Packaging;
|
||||
|
||||
public class Zstd
|
||||
{
|
||||
private readonly string _zstdExe;
|
||||
|
||||
public Zstd(string zstdExe)
|
||||
{
|
||||
_zstdExe = zstdExe;
|
||||
}
|
||||
|
||||
public void CreatePatch(string oldFile, string newFile, string outputFile, DeltaMode mode)
|
||||
{
|
||||
if (mode == DeltaMode.None)
|
||||
throw new ArgumentException("DeltaMode.None is not supported.", nameof(mode));
|
||||
|
||||
List<string> args = new() {
|
||||
"--patch-from", oldFile,
|
||||
"-o", outputFile,
|
||||
"--force",
|
||||
"--single-thread",
|
||||
newFile,
|
||||
};
|
||||
|
||||
var windowLog = FIO_highbit64(new FileInfo(oldFile).Length) + 1;
|
||||
if (windowLog >= 27) {
|
||||
args.Add($"--long={windowLog}");
|
||||
}
|
||||
|
||||
if (windowLog > 30) {
|
||||
throw new UserInfoException($"The file '{Path.GetFileName(oldFile)}' is too large for delta compression. You can disable delta generation using '--delta none'.");
|
||||
}
|
||||
|
||||
if (mode == DeltaMode.BestSize) {
|
||||
args.Add("-19");
|
||||
args.Add("--zstd=targetLength=4096");
|
||||
args.Add("--zstd=chainLog=30");
|
||||
}
|
||||
|
||||
Exe.InvokeAndThrowIfNonZero(_zstdExe, args, null);
|
||||
}
|
||||
|
||||
public void ApplyPatch(string baseFile, string patchFile, string outputFile)
|
||||
{
|
||||
List<string> args = new() {
|
||||
"--decompress",
|
||||
"--patch-from", baseFile,
|
||||
"-o", outputFile,
|
||||
"--force",
|
||||
patchFile,
|
||||
};
|
||||
|
||||
var windowLog = FIO_highbit64(new FileInfo(baseFile).Length) + 1;
|
||||
if (windowLog >= 27) {
|
||||
args.Add($"--long={windowLog}");
|
||||
}
|
||||
|
||||
Exe.InvokeAndThrowIfNonZero(_zstdExe, args, null);
|
||||
}
|
||||
|
||||
private int FIO_highbit64(long v)
|
||||
{
|
||||
int count = 0;
|
||||
v >>= 1;
|
||||
while (v > 0) { v >>= 1; count++; }
|
||||
return count;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Velopack.Packaging;
|
||||
using Velopack.Packaging.Exceptions;
|
||||
using Velopack.Vpk.Commands;
|
||||
using Velopack.Vpk.Updates;
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ using Microsoft.Extensions.Hosting;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
using Spectre.Console;
|
||||
using Velopack.Packaging;
|
||||
using Velopack.Vpk.Commands;
|
||||
using Velopack.Vpk.Compat;
|
||||
using Velopack.Vpk.Logging;
|
||||
|
||||
@@ -23,27 +23,12 @@
|
||||
<None Include="..\..\README_NUGET.md" Pack="true" PackagePath="\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
|
||||
<None Include="..\Rust\target\debug\update" CopyToOutputDirectory="Always" Condition="$([MSBuild]::IsOSPlatform('OSX')) or $([MSBuild]::IsOSPlatform('Linux'))" />
|
||||
<None Include="..\Rust\target\debug\update.exe" CopyToOutputDirectory="Always" Condition="$([MSBuild]::IsOSPlatform('Windows'))" />
|
||||
<None Include="..\Rust\target\debug\setup.exe" CopyToOutputDirectory="Always" Condition="$([MSBuild]::IsOSPlatform('Windows'))" />
|
||||
<None Include="..\Rust\target\debug\stub.exe" CopyToOutputDirectory="Always" Condition="$([MSBuild]::IsOSPlatform('Windows'))" />
|
||||
<None Include="..\..\vendor\appimagetool-x86_64.AppImage" CopyToOutputDirectory="Always" />
|
||||
<None Include="..\..\vendor\rcedit.exe" CopyToOutputDirectory="Always" />
|
||||
<None Include="..\..\vendor\zstd.exe" CopyToOutputDirectory="Always" />
|
||||
<None Include="..\..\vendor\signtool.exe" CopyToOutputDirectory="Always" />
|
||||
<None Include="..\..\Velopack.entitlements" CopyToOutputDirectory="Always" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(PackRustAssets)' == 'true'">
|
||||
<None Include="..\Rust\target\release\UpdateMac" Pack="true" PackagePath="vendor" />
|
||||
<None Include="..\Rust\target\release\UpdateNix" Pack="true" PackagePath="vendor" />
|
||||
<None Include="..\Rust\target\release\update.exe" Pack="true" PackagePath="vendor" />
|
||||
<None Include="..\Rust\target\release\setup.exe" Pack="true" PackagePath="vendor" />
|
||||
<None Include="..\Rust\target\release\stub.exe" Pack="true" PackagePath="vendor" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\..\vendor\appimagetool-x86_64.AppImage" Pack="true" PackagePath="vendor" />
|
||||
<None Include="..\..\vendor\rcedit.exe" Pack="true" PackagePath="vendor" />
|
||||
<None Include="..\..\vendor\zstd.exe" Pack="true" PackagePath="vendor" />
|
||||
|
||||
@@ -1,30 +1,24 @@
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
// https://dev.to/emrahsungu/how-to-compare-two-files-using-net-really-really-fast-2pd9
|
||||
// https://github.com/SnowflakePowered/vcdiff
|
||||
|
||||
namespace Velopack.Compression
|
||||
{
|
||||
internal class DeltaPackage
|
||||
internal abstract class DeltaPackage
|
||||
{
|
||||
private readonly ILogger _log;
|
||||
private readonly string _updatePath;
|
||||
private readonly string _baseTempDir;
|
||||
protected readonly ILogger Log;
|
||||
protected readonly string BaseTempDir;
|
||||
|
||||
private static Regex DIFF_SUFFIX = new Regex(@"\.(bs|zs)?diff$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
public DeltaPackage(ILogger logger, string baseTmpDir, string updateExePath)
|
||||
public DeltaPackage(ILogger logger, string baseTmpDir)
|
||||
{
|
||||
_log = logger;
|
||||
_baseTempDir = baseTmpDir;
|
||||
_updatePath = updateExePath;
|
||||
Log = logger;
|
||||
BaseTempDir = baseTmpDir;
|
||||
}
|
||||
|
||||
public void ApplyDeltaPackageFast(string workingPath, string deltaPackageZip, Action<int> progress = null)
|
||||
@@ -33,10 +27,10 @@ namespace Velopack.Compression
|
||||
|
||||
if (deltaPackageZip is null) throw new ArgumentNullException(nameof(deltaPackageZip));
|
||||
|
||||
_log.Info($"Applying delta package from {deltaPackageZip} to delta staging directory.");
|
||||
Log.Info($"Applying delta package from {deltaPackageZip} to delta staging directory.");
|
||||
|
||||
using var _1 = Utility.GetTempDirectory(out var deltaPath, _baseTempDir);
|
||||
EasyZip.ExtractZipToDirectory(_log, deltaPackageZip, deltaPath);
|
||||
using var _1 = Utility.GetTempDirectory(out var deltaPath, BaseTempDir);
|
||||
EasyZip.ExtractZipToDirectory(Log, deltaPackageZip, deltaPath);
|
||||
progress(10);
|
||||
|
||||
var pathsVisited = new List<string>();
|
||||
@@ -68,7 +62,7 @@ namespace Velopack.Compression
|
||||
.Select(x => x.FullName.Replace(workingPath + Path.DirectorySeparatorChar, "").ToLowerInvariant())
|
||||
.Where(x => x.StartsWith("lib", StringComparison.InvariantCultureIgnoreCase) && !pathsVisited.Contains(x))
|
||||
.ForEach(x => {
|
||||
_log.Trace($"{x} was in old package but not in new one, deleting");
|
||||
Log.Trace($"{x} was in old package but not in new one, deleting");
|
||||
File.Delete(Path.Combine(workingPath, x));
|
||||
});
|
||||
|
||||
@@ -79,7 +73,7 @@ namespace Velopack.Compression
|
||||
deltaPathRelativePaths
|
||||
.Where(x => !x.StartsWith("lib", StringComparison.InvariantCultureIgnoreCase))
|
||||
.ForEach(x => {
|
||||
_log.Trace($"Writing metadata file: {x}");
|
||||
Log.Trace($"Writing metadata file: {x}");
|
||||
File.Copy(Path.Combine(deltaPath, x), Path.Combine(workingPath, x), true);
|
||||
});
|
||||
|
||||
@@ -89,50 +83,42 @@ namespace Velopack.Compression
|
||||
.Where(x => !x.StartsWith("lib", StringComparison.InvariantCultureIgnoreCase)
|
||||
&& !deltaPathRelativePaths.Contains(x, StringComparer.InvariantCultureIgnoreCase))
|
||||
.ForEach(x => {
|
||||
_log.Trace($"Deleting removed metadata file: {x}");
|
||||
Log.Trace($"Deleting removed metadata file: {x}");
|
||||
File.Delete(Path.Combine(workingPath, x));
|
||||
});
|
||||
|
||||
progress(100);
|
||||
}
|
||||
|
||||
protected abstract void ApplyZstdPatch(string baseFile, string patchFile, string outputFile);
|
||||
|
||||
void applyDiffToFile(string deltaPath, string relativeFilePath, string workingDirectory)
|
||||
{
|
||||
var inputFile = Path.Combine(deltaPath, relativeFilePath);
|
||||
var finalTarget = Path.Combine(workingDirectory, DIFF_SUFFIX.Replace(relativeFilePath, ""));
|
||||
|
||||
using var _d = Utility.GetTempFileName(out var tempTargetFile, _baseTempDir);
|
||||
using var _d = Utility.GetTempFileName(out var tempTargetFile, BaseTempDir);
|
||||
|
||||
// NB: Zero-length diffs indicate the file hasn't actually changed
|
||||
if (new FileInfo(inputFile).Length == 0) {
|
||||
_log.Trace($"{relativeFilePath} exists unchanged, skipping");
|
||||
Log.Trace($"{relativeFilePath} exists unchanged, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
if (relativeFilePath.EndsWith(".zsdiff", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
var psi = new ProcessStartInfo(_updatePath);
|
||||
psi.AppendArgumentListSafe(new string[] { "patch", "--nocolor", "--old", finalTarget, "--patch", inputFile, "--output", tempTargetFile }, out var _);
|
||||
psi.CreateNoWindow = true;
|
||||
_log.Trace($"Applying zstd diff to {relativeFilePath}");
|
||||
var p = psi.StartRedirectOutputToILogger(_log, LogLevel.Debug);
|
||||
if (!p.WaitForExit(60_000)) {
|
||||
p.Kill();
|
||||
throw new TimeoutException("zstd patch process timed out (60s).");
|
||||
}
|
||||
if (p.ExitCode != 0) {
|
||||
throw new Exception($"zstd patch process failed with exit code {p.ExitCode}.");
|
||||
}
|
||||
Log.Trace($"Applying zstd diff to {relativeFilePath}");
|
||||
ApplyZstdPatch(finalTarget, inputFile, tempTargetFile);
|
||||
verifyPatchedFile(relativeFilePath, inputFile, tempTargetFile);
|
||||
} else if (relativeFilePath.EndsWith(".bsdiff", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
using (var of = File.OpenWrite(tempTargetFile))
|
||||
using (var inf = File.OpenRead(finalTarget)) {
|
||||
_log.Trace($"Applying bsdiff to {relativeFilePath}");
|
||||
Log.Trace($"Applying bsdiff to {relativeFilePath}");
|
||||
BinaryPatchUtility.Apply(inf, () => File.OpenRead(inputFile), of);
|
||||
}
|
||||
|
||||
verifyPatchedFile(relativeFilePath, inputFile, tempTargetFile);
|
||||
} else if (relativeFilePath.EndsWith(".diff", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
_log.Trace($"Applying msdiff to {relativeFilePath}");
|
||||
Log.Trace($"Applying msdiff to {relativeFilePath}");
|
||||
|
||||
if (VelopackRuntimeInfo.IsWindows) {
|
||||
MsDeltaCompression.ApplyDelta(inputFile, finalTarget, tempTargetFile);
|
||||
@@ -144,7 +130,7 @@ namespace Velopack.Compression
|
||||
} else {
|
||||
using (var of = File.OpenWrite(tempTargetFile))
|
||||
using (var inf = File.OpenRead(inputFile)) {
|
||||
_log.Trace($"Adding new file: {relativeFilePath}");
|
||||
Log.Trace($"Adding new file: {relativeFilePath}");
|
||||
inf.CopyTo(of);
|
||||
}
|
||||
}
|
||||
@@ -164,12 +150,12 @@ namespace Velopack.Compression
|
||||
var actualReleaseEntry = ReleaseEntry.GenerateFromFile(tempTargetFile);
|
||||
|
||||
if (expectedReleaseEntry.Filesize != actualReleaseEntry.Filesize) {
|
||||
_log.Error($"Patched file {relativeFilePath} has incorrect size, expected {expectedReleaseEntry.Filesize}, got {actualReleaseEntry.Filesize}");
|
||||
Log.Error($"Patched file {relativeFilePath} has incorrect size, expected {expectedReleaseEntry.Filesize}, got {actualReleaseEntry.Filesize}");
|
||||
throw new ChecksumFailedException(relativeFilePath);
|
||||
}
|
||||
|
||||
if (expectedReleaseEntry.SHA1 != actualReleaseEntry.SHA1) {
|
||||
_log.Error($"Patched file {relativeFilePath} has incorrect SHA1, expected {expectedReleaseEntry.SHA1}, got {actualReleaseEntry.SHA1}");
|
||||
Log.Error($"Patched file {relativeFilePath} has incorrect SHA1, expected {expectedReleaseEntry.SHA1}, got {actualReleaseEntry.SHA1}");
|
||||
throw new ChecksumFailedException(relativeFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
31
src/Velopack/Compression/DeltaUpdateExe.cs
Normal file
31
src/Velopack/Compression/DeltaUpdateExe.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Velopack.Compression
|
||||
{
|
||||
internal class DeltaUpdateExe : DeltaPackage
|
||||
{
|
||||
private readonly string _updateExePath;
|
||||
|
||||
public DeltaUpdateExe(ILogger logger, string baseTmpDir, string updateExePath) : base(logger, baseTmpDir)
|
||||
{
|
||||
_updateExePath = updateExePath;
|
||||
}
|
||||
|
||||
protected override void ApplyZstdPatch(string baseFile, string patchFile, string outputFile)
|
||||
{
|
||||
var psi = new ProcessStartInfo(_updateExePath);
|
||||
psi.AppendArgumentListSafe(new string[] { "patch", "--nocolor", "--old", baseFile, "--patch", patchFile, "--output", outputFile }, out var _);
|
||||
psi.CreateNoWindow = true;
|
||||
var p = psi.StartRedirectOutputToILogger(Log, LogLevel.Debug);
|
||||
if (!p.WaitForExit(30_000)) {
|
||||
p.Kill();
|
||||
throw new TimeoutException("zstd patch process timed out (30s).");
|
||||
}
|
||||
if (p.ExitCode != 0) {
|
||||
throw new Exception($"zstd patch process failed with exit code {p.ExitCode}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -391,7 +391,7 @@ namespace Velopack
|
||||
|
||||
// applying deltas accounts for 50%-100% of progress
|
||||
double progressStepSize = 100d / releasesToDownload.Length;
|
||||
var builder = new DeltaPackage(Log, Locator.AppTempDir, Locator.UpdateExePath);
|
||||
var builder = new DeltaUpdateExe(Log, Locator.AppTempDir, Locator.UpdateExePath);
|
||||
for (var i = 0; i < releasesToDownload.Length; i++) {
|
||||
var rel = releasesToDownload[i];
|
||||
double baseProgress = i * progressStepSize;
|
||||
|
||||
@@ -7,6 +7,7 @@ using Microsoft.Win32;
|
||||
using NuGet.Packaging;
|
||||
using Velopack.Compression;
|
||||
using Velopack.Packaging.Commands;
|
||||
using Velopack.Packaging.Exceptions;
|
||||
using Velopack.Packaging.Windows.Commands;
|
||||
using Velopack.Windows;
|
||||
|
||||
@@ -38,8 +39,8 @@ public class WindowsPackTests
|
||||
var id = "Test.Squirrel-App";
|
||||
var version = "1.0.0";
|
||||
|
||||
File.Copy(HelperFile.FindTestFile(exe), Path.Combine(tmpOutput, exe));
|
||||
File.Copy(HelperFile.FindTestFile(pdb), Path.Combine(tmpOutput, pdb));
|
||||
PathHelper.CopyRustAssetTo(exe, tmpOutput);
|
||||
PathHelper.CopyRustAssetTo(pdb, tmpOutput);
|
||||
|
||||
var options = new WindowsPackOptions {
|
||||
EntryExecutableName = exe,
|
||||
@@ -103,8 +104,8 @@ public class WindowsPackTests
|
||||
var id = "Test.Squirrel-App";
|
||||
var version = "1.0.0";
|
||||
|
||||
File.Copy(HelperFile.FindTestFile(exe), Path.Combine(tmpOutput, exe));
|
||||
File.Copy(HelperFile.FindTestFile(pdb), Path.Combine(tmpOutput, pdb));
|
||||
PathHelper.CopyRustAssetTo(exe, tmpOutput);
|
||||
PathHelper.CopyRustAssetTo(pdb, tmpOutput);
|
||||
|
||||
var options = new WindowsPackOptions {
|
||||
EntryExecutableName = exe,
|
||||
@@ -169,8 +170,8 @@ public class WindowsPackTests
|
||||
var id = "Test.Squirrel-App";
|
||||
var version = "1.0.0";
|
||||
|
||||
File.Copy(HelperFile.FindTestFile(exe), Path.Combine(tmpOutput, exe));
|
||||
File.Copy(HelperFile.FindTestFile(pdb), Path.Combine(tmpOutput, pdb));
|
||||
PathHelper.CopyRustAssetTo(exe, tmpOutput);
|
||||
PathHelper.CopyRustAssetTo(pdb, tmpOutput);
|
||||
|
||||
var options = new WindowsPackOptions {
|
||||
EntryExecutableName = exe,
|
||||
@@ -202,8 +203,8 @@ public class WindowsPackTests
|
||||
var id = "Test.Squirrel-App";
|
||||
var version = "1.0.0";
|
||||
|
||||
File.Copy(HelperFile.FindTestFile(exe), Path.Combine(tmpOutput, exe));
|
||||
File.Copy(HelperFile.FindTestFile(pdb), Path.Combine(tmpOutput, pdb));
|
||||
PathHelper.CopyRustAssetTo(exe, tmpOutput);
|
||||
PathHelper.CopyRustAssetTo(pdb, tmpOutput);
|
||||
|
||||
var options = new WindowsPackOptions {
|
||||
EntryExecutableName = exe,
|
||||
@@ -240,8 +241,8 @@ public class WindowsPackTests
|
||||
var id = "Test.Squirrel-App";
|
||||
var version = "1.0.0";
|
||||
|
||||
File.Copy(HelperFile.FindTestFile(exe), Path.Combine(tmpOutput, exe));
|
||||
File.Copy(HelperFile.FindTestFile(pdb), Path.Combine(tmpOutput, pdb));
|
||||
PathHelper.CopyRustAssetTo(exe, tmpOutput);
|
||||
PathHelper.CopyRustAssetTo(pdb, tmpOutput);
|
||||
|
||||
var options = new WindowsPackOptions {
|
||||
EntryExecutableName = exe,
|
||||
@@ -272,8 +273,8 @@ public class WindowsPackTests
|
||||
var id = "Test.Squirrel-App";
|
||||
var version = "1.0.0";
|
||||
|
||||
File.Copy(HelperFile.FindTestFile(exe), Path.Combine(tmpOutput, exe));
|
||||
File.Copy(HelperFile.FindTestFile(pdb), Path.Combine(tmpOutput, pdb));
|
||||
PathHelper.CopyRustAssetTo(exe, tmpOutput);
|
||||
PathHelper.CopyRustAssetTo(pdb, tmpOutput);
|
||||
|
||||
var options = new WindowsPackOptions {
|
||||
EntryExecutableName = exe,
|
||||
|
||||
Reference in New Issue
Block a user