diff --git a/src/Velopack.Packaging.Windows/Rcedit.cs b/src/Velopack.Packaging.Windows/Rcedit.cs index ac8b25b3..69669428 100644 --- a/src/Velopack.Packaging.Windows/Rcedit.cs +++ b/src/Velopack.Packaging.Windows/Rcedit.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Runtime.Versioning; using System.Text; using System.Threading.Tasks; +using Velopack.NuGet; namespace Velopack.Packaging.Windows { @@ -17,7 +18,7 @@ namespace Velopack.Packaging.Windows } [SupportedOSPlatform("windows")] - public static void SetPEVersionBlockFromPackageInfo(string exePath, NuGet.IPackage package, string iconPath = null) + public static void SetPEVersionBlockFromPackageInfo(string exePath, NuspecManifest package, string iconPath = null) { var realExePath = Path.GetFullPath(exePath); diff --git a/src/Velopack/Compression/BZip2Stream.cs b/src/Velopack/Compression/BZip2Stream.cs index 6962ecf3..4442451a 100644 --- a/src/Velopack/Compression/BZip2Stream.cs +++ b/src/Velopack/Compression/BZip2Stream.cs @@ -1,4 +1,5 @@ -using System; +#nullable disable +using System; using System.Diagnostics.CodeAnalysis; using System.IO; using System.IO.Compression; diff --git a/src/Velopack/Compression/BinaryPatchUtility.cs b/src/Velopack/Compression/BinaryPatchUtility.cs index d4598159..0df5d978 100644 --- a/src/Velopack/Compression/BinaryPatchUtility.cs +++ b/src/Velopack/Compression/BinaryPatchUtility.cs @@ -1,4 +1,5 @@ -using System; +#nullable disable +using System; using System.Diagnostics.CodeAnalysis; using System.IO; using System.IO.Compression; diff --git a/src/Velopack/Compression/DeltaPackage.cs b/src/Velopack/Compression/DeltaPackage.cs index 09cff290..138d0d2b 100644 --- a/src/Velopack/Compression/DeltaPackage.cs +++ b/src/Velopack/Compression/DeltaPackage.cs @@ -21,7 +21,7 @@ namespace Velopack.Compression BaseTempDir = baseTmpDir; } - public void ApplyDeltaPackageFast(string workingPath, string deltaPackageZip, Action progress = null) + public void ApplyDeltaPackageFast(string workingPath, string deltaPackageZip, Action? progress = null) { progress = progress ?? (x => { }); @@ -137,7 +137,7 @@ namespace Velopack.Compression if (File.Exists(finalTarget)) File.Delete(finalTarget); - var targetPath = Directory.GetParent(finalTarget); + var targetPath = Directory.GetParent(finalTarget)!; if (!targetPath.Exists) targetPath.Create(); File.Move(tempTargetFile, finalTarget); diff --git a/src/Velopack/Compression/EasyZip.cs b/src/Velopack/Compression/EasyZip.cs index 983b0e59..387c7fd6 100644 --- a/src/Velopack/Compression/EasyZip.cs +++ b/src/Velopack/Compression/EasyZip.cs @@ -18,7 +18,7 @@ namespace Velopack.Compression ZipFile.ExtractToDirectory(inputFile, outputDirectory); } - public static void CreateZipFromDirectory(ILogger logger, string outputFile, string directoryToCompress, Action progress = null) + public static void CreateZipFromDirectory(ILogger logger, string outputFile, string directoryToCompress, Action? progress = null) { progress ??= (x => { }); logger.Debug($"Compressing '{directoryToCompress}' to '{outputFile}' using System.IO.Compression..."); @@ -33,7 +33,7 @@ namespace Velopack.Compression } } - public static async Task CreateZipFromDirectoryAsync(ILogger logger, string outputFile, string directoryToCompress, Action progress = null) + public static async Task CreateZipFromDirectoryAsync(ILogger logger, string outputFile, string directoryToCompress, Action? progress = null) { progress ??= (x => { }); logger.Debug($"Compressing '{directoryToCompress}' to '{outputFile}' using System.IO.Compression..."); diff --git a/src/Velopack/Compression/MsDeltaCompression.cs b/src/Velopack/Compression/MsDeltaCompression.cs index 5f88a80b..6c1700f4 100644 --- a/src/Velopack/Compression/MsDeltaCompression.cs +++ b/src/Velopack/Compression/MsDeltaCompression.cs @@ -1,6 +1,4 @@ -#nullable enable - -using System; +using System; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; diff --git a/src/Velopack/Internal/EnumerableExtensions.cs b/src/Velopack/Internal/EnumerableExtensions.cs index d84afeb4..79aa473b 100644 --- a/src/Velopack/Internal/EnumerableExtensions.cs +++ b/src/Velopack/Internal/EnumerableExtensions.cs @@ -22,7 +22,7 @@ namespace Velopack /// Eg. "Invalid {is}. One {what} expected in {in}. None was found." /// Eg. "Invalid {is}. Only a single {what} expected in {in}. There were 2 or more." /// - public static T ContextualSingle(this IEnumerable source, string strIs, string strWhat, string strIn = null) + public static T ContextualSingle(this IEnumerable source, string strIs, string strWhat, string? strIn = null) { T result; using (var e = source.GetEnumerator()) { diff --git a/src/Velopack/Internal/ProcessExtensions.cs b/src/Velopack/Internal/ProcessExtensions.cs index abc199d5..46b4e02c 100644 --- a/src/Velopack/Internal/ProcessExtensions.cs +++ b/src/Velopack/Internal/ProcessExtensions.cs @@ -39,6 +39,8 @@ namespace Velopack psi.UseShellExecute = false; var p = Process.Start(psi); + if (p == null) throw new Exception("Process.Start returned null."); + p.BeginErrorReadLine(); p.BeginOutputReadLine(); @@ -63,6 +65,8 @@ namespace Velopack psi.UseShellExecute = false; var p = Process.Start(psi); + if (p == null) throw new Exception("Process.Start returned null."); + if (!p.WaitForExit(timeoutMs)) { p.Kill(); throw new TimeoutException("Process did not exit within allotted time."); diff --git a/src/Velopack/Internal/SimpleJson.cs b/src/Velopack/Internal/SimpleJson.cs index 6bb9b4de..9c461156 100644 --- a/src/Velopack/Internal/SimpleJson.cs +++ b/src/Velopack/Internal/SimpleJson.cs @@ -34,7 +34,7 @@ namespace Velopack.Json Converters = { new JsonStringEnumConverter(), new SemanticVersionConverter() }, }; - public static T DeserializeObject(string json) + public static T? DeserializeObject(string json) { return JsonSerializer.Deserialize(json, Options); } @@ -47,9 +47,11 @@ namespace Velopack.Json internal class SemanticVersionConverter : JsonConverter { - public override SemanticVersion Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override SemanticVersion? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - return SemanticVersion.Parse(reader.GetString()); + var str = reader.GetString(); + if (str == null) return null; + return SemanticVersion.Parse(str); } public override void Write(Utf8JsonWriter writer, SemanticVersion value, JsonSerializerOptions options) @@ -76,7 +78,7 @@ namespace Velopack.Json NullValueHandling = NullValueHandling.Ignore, }; - public static T DeserializeObject(string json) + public static T? DeserializeObject(string json) { return JsonConvert.DeserializeObject(json, Options); } @@ -89,15 +91,20 @@ namespace Velopack.Json internal class SemanticVersionConverter : JsonConverter { - public override SemanticVersion ReadJson(JsonReader reader, Type objectType, SemanticVersion existingValue, bool hasExistingValue, JsonSerializer serializer) + public override SemanticVersion? ReadJson(JsonReader reader, Type objectType, SemanticVersion? existingValue, bool hasExistingValue, JsonSerializer serializer) { - string s = (string) reader.Value; + string? s = reader.Value as string; + if (s == null) return null; return SemanticVersion.Parse(s); } - public override void WriteJson(JsonWriter writer, SemanticVersion value, JsonSerializer serializer) + public override void WriteJson(JsonWriter writer, SemanticVersion? value, JsonSerializer serializer) { - writer.WriteValue(value.ToFullString()); + if (value != null) { + writer.WriteValue(value.ToFullString()); + } else { + writer.WriteNull(); + } } } diff --git a/src/Velopack/Internal/Utility.cs b/src/Velopack/Internal/Utility.cs index 62212e47..b901696b 100644 --- a/src/Velopack/Internal/Utility.cs +++ b/src/Velopack/Internal/Utility.cs @@ -10,6 +10,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; namespace Velopack { @@ -56,10 +57,6 @@ namespace Velopack { byte[] output = { }; - if (content == null) { - goto done; - } - Func matches = (bom, src) => { if (src.Length < bom.Length) return false; @@ -86,7 +83,6 @@ namespace Velopack output = content; } - done: if (output.Length > 0) { Buffer.BlockCopy(content, content.Length - output.Length, output, 0, output.Length); } @@ -94,9 +90,9 @@ namespace Velopack return Encoding.UTF8.GetString(output); } - public static bool TryParseEnumU16(ushort enumValue, out TEnum retVal) + public static bool TryParseEnumU16(ushort enumValue, out TEnum? retVal) { - retVal = default(TEnum); + retVal = default; bool success = Enum.IsDefined(typeof(TEnum), enumValue); if (success) { retVal = (TEnum) Enum.ToObject(typeof(TEnum), enumValue); @@ -148,21 +144,12 @@ namespace Velopack public static IEnumerable GetAllFilesRecursively(this DirectoryInfo rootPath) { - Contract.Requires(rootPath != null); - + if (rootPath == null) return Enumerable.Empty(); return rootPath.EnumerateFiles("*", SearchOption.AllDirectories); } - public static IEnumerable GetAllFilePathsRecursively(string rootPath) - { - Contract.Requires(rootPath != null); - - return Directory.EnumerateFiles(rootPath, "*", SearchOption.AllDirectories); - } - public static string CalculateFileSHA1(string filePath) { - Contract.Requires(filePath != null); var bufferSize = 1000000; // 1mb using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize)) { return CalculateStreamSHA1(stream); @@ -171,8 +158,6 @@ namespace Velopack public static string CalculateStreamSHA1(Stream file) { - Contract.Requires(file != null && file.CanRead); - using (var sha1 = SHA1.Create()) { return BitConverter.ToString(sha1.ComputeHash(file)).Replace("-", String.Empty); } @@ -195,33 +180,18 @@ namespace Velopack // default RELEASES file name for each platform. if (VelopackRuntimeInfo.IsOSX) return "RELEASES-osx"; if (VelopackRuntimeInfo.IsLinux) return "RELEASES-linux"; - if (VelopackRuntimeInfo.IsWindows) return "RELEASES"; - } - // if the channel is an empty string or "win", we use the default RELEASES file name. - if (String.IsNullOrWhiteSpace(channel) || channel.ToLower() == "win") { return "RELEASES"; + } else { + // if the channel is an empty string or "win", we use the default RELEASES file name. + if (String.IsNullOrWhiteSpace(channel) || channel.ToLower() == "win") { + return "RELEASES"; + } + // all other cases the RELEASES file includes the channel name. + return $"RELEASES-{channel.ToLower()}"; } - // all other cases the RELEASES file includes the channel name. - return $"RELEASES-{channel.ToLower()}"; } - public static async Task CopyToAsync(string from, string to) - { - Contract.Requires(!String.IsNullOrEmpty(from) && File.Exists(from)); - Contract.Requires(!String.IsNullOrEmpty(to)); - - if (!File.Exists(from)) { - //Log().Warn("The file {0} does not exist", from); - - // TODO: should we fail this operation? - return; - } - - // XXX: SafeCopy - await Task.Run(() => File.Copy(from, to, true)).ConfigureAwait(false); - } - - public static void Retry(this Action block, int retries = 4, int retryDelay = 250, ILogger logger = null) + public static void Retry(this Action block, int retries = 4, int retryDelay = 250, ILogger? logger = null) { Retry(() => { block(); @@ -229,7 +199,7 @@ namespace Velopack }, retries, retryDelay, logger); } - public static T Retry(this Func block, int retries = 4, int retryDelay = 250, ILogger logger = null) + public static T Retry(this Func block, int retries = 4, int retryDelay = 250, ILogger? logger = null) { Contract.Requires(retries > 0); @@ -246,7 +216,7 @@ namespace Velopack } } - public static Task RetryAsync(this Func block, int retries = 4, int retryDelay = 250, ILogger logger = null) + public static Task RetryAsync(this Func block, int retries = 4, int retryDelay = 250, ILogger? logger = null) { return RetryAsync(async () => { await block().ConfigureAwait(false); @@ -254,7 +224,7 @@ namespace Velopack }, retries, retryDelay, logger); } - public static async Task RetryAsync(this Func> block, int retries = 4, int retryDelay = 250, ILogger logger = null) + public static async Task RetryAsync(this Func> block, int retries = 4, int retryDelay = 250, ILogger? logger = null) { while (true) { try { @@ -322,7 +292,7 @@ namespace Velopack string name = "temp." + i; var target = Path.Combine(tempDir, name); - FileSystemInfo info = null; + FileSystemInfo? info = null; if (Directory.Exists(target)) info = new DirectoryInfo(target); else if (File.Exists(target)) info = new FileInfo(target); @@ -378,10 +348,11 @@ namespace Velopack /// Try to rename this object first before deleting. Can help prevent partial delete of folders. /// Logger for diagnostic messages. /// True if the file system object was deleted, false otherwise. - public static bool DeleteFileOrDirectoryHard(string path, bool throwOnFailure = true, bool renameFirst = false, ILogger logger = null) + public static bool DeleteFileOrDirectoryHard(string path, bool throwOnFailure = true, bool renameFirst = false, ILogger? logger = null) { + logger ??= NullLogger.Instance; Contract.Requires(!String.IsNullOrEmpty(path)); - logger?.Debug($"Starting to delete: {path}"); + logger.Debug($"Starting to delete: {path}"); try { if (File.Exists(path)) { @@ -402,7 +373,7 @@ namespace Velopack return true; } catch (Exception ex) { - logger?.Error(ex, $"Unable to delete '{path}'"); + logger.Error(ex, $"Unable to delete '{path}'"); if (throwOnFailure) throw; return false; @@ -426,7 +397,7 @@ namespace Velopack } } } catch (Exception ex) { - logger?.Warn(ex, $"Unable to traverse children of '{fileSystemInfo.FullName}'"); + logger.Warn(ex, $"Unable to traverse children of '{fileSystemInfo.FullName}'"); } // finally, delete myself, we should try this even if deleting children failed @@ -610,7 +581,7 @@ namespace Velopack using (var algorithm = SHA1.Create()) { algorithm.TransformBlock(namespaceBytes, 0, namespaceBytes.Length, null, 0); algorithm.TransformFinalBlock(nameBytes, 0, nameBytes.Length); - hash = algorithm.Hash; + hash = algorithm.Hash!; } // most bytes from the hash are copied straight to the bytes of diff --git a/src/Velopack/Locators/IVelopackLocator.cs b/src/Velopack/Locators/IVelopackLocator.cs index 63cd2ac9..9ee2d555 100644 --- a/src/Velopack/Locators/IVelopackLocator.cs +++ b/src/Velopack/Locators/IVelopackLocator.cs @@ -10,34 +10,34 @@ namespace Velopack.Locators public interface IVelopackLocator { /// The unique application Id. This is used in various app paths. - public string AppId { get; } + public string? AppId { get; } /// /// The root directory of the application. On Windows, this folder contains all /// the application files, but that may not be the case on other operating systems. /// - public string RootAppDir { get; } + public string? RootAppDir { get; } /// The directory in which nupkg files are stored for this application. - public string PackagesDir { get; } + public string? PackagesDir { get; } /// The directory in which versioned application files are stored. - public string AppContentDir { get; } + public string? AppContentDir { get; } /// The temporary directory for this application. - public string AppTempDir { get; } + public string? AppTempDir { get; } /// The path to the current Update.exe or similar on other operating systems. - public string UpdateExePath { get; } + public string? UpdateExePath { get; } /// The currently installed version of the application, or null if the app is not installed. - public SemanticVersion CurrentlyInstalledVersion { get; } + public SemanticVersion? CurrentlyInstalledVersion { get; } /// The path from to this executable. - public string ThisExeRelativePath { get; } + public string? ThisExeRelativePath { get; } /// The release channel this package was built for. - public string Channel { get; } + public string? Channel { get; } /// /// Finds .nupkg files in the PackagesDir and returns a list of ReleaseEntryName objects. @@ -47,7 +47,7 @@ namespace Velopack.Locators /// /// Finds latest .nupkg file in the PackagesDir or null if not found. /// - public VelopackAsset GetLatestLocalFullPackage(); + public VelopackAsset? GetLatestLocalFullPackage(); /// /// Unique identifier for this user which is used to calculate whether this user is eligible for diff --git a/src/Velopack/Locators/LinuxVelopackLocator.cs b/src/Velopack/Locators/LinuxVelopackLocator.cs index 161d7c52..47e1315a 100644 --- a/src/Velopack/Locators/LinuxVelopackLocator.cs +++ b/src/Velopack/Locators/LinuxVelopackLocator.cs @@ -20,37 +20,37 @@ namespace Velopack.Locators public class LinuxVelopackLocator : VelopackLocator { /// - public override string AppId { get; } + public override string? AppId { get; } /// - public override string RootAppDir { get; } + public override string? RootAppDir { get; } /// - public override string UpdateExePath { get; } + public override string? UpdateExePath { get; } /// - public override SemanticVersion CurrentlyInstalledVersion { get; } + public override SemanticVersion? CurrentlyInstalledVersion { get; } /// - public override string AppContentDir { get; } + public override string? AppContentDir { get; } /// - public override string Channel { get; } + public override string? Channel { get; } /// - public override string AppTempDir => CreateSubDirIfDoesNotExist(Utility.GetDefaultTempBaseDirectory(), AppId); + public override string? AppTempDir => CreateSubDirIfDoesNotExist(Utility.GetDefaultTempBaseDirectory(), AppId); /// - public override string PackagesDir => CreateSubDirIfDoesNotExist(PersistentTempDir, "packages"); + public override string? PackagesDir => CreateSubDirIfDoesNotExist(PersistentTempDir, "packages"); /// /var/tmp/{velopack}/{appid}, for storing app specific files which need to be preserved. - public string PersistentTempDir => CreateSubDirIfDoesNotExist(PersistentVelopackDir, AppId); + public string? PersistentTempDir => CreateSubDirIfDoesNotExist(PersistentVelopackDir, AppId); /// A pointer to /var/tmp/{velopack}, a location on linux which is semi-persistent. - public string PersistentVelopackDir => CreateSubDirIfDoesNotExist("/var/tmp", "velopack"); + public string? PersistentVelopackDir => CreateSubDirIfDoesNotExist("/var/tmp", "velopack"); /// File path of the .AppImage which mounted and ran this application. - public string AppImagePath => Environment.GetEnvironmentVariable("APPIMAGE"); + public string? AppImagePath => Environment.GetEnvironmentVariable("APPIMAGE"); /// /// Creates a new and auto-detects the @@ -63,7 +63,7 @@ namespace Velopack.Locators throw new NotSupportedException("Cannot instantiate LinuxVelopackLocator on a non-linux system."); Log.Info($"Initialising {nameof(LinuxVelopackLocator)}"); - + // are we inside a mounted .AppImage? var ourPath = VelopackRuntimeInfo.EntryExePath; var ix = ourPath.IndexOf("/usr/bin/", StringComparison.InvariantCultureIgnoreCase); diff --git a/src/Velopack/Locators/OsxVelopackLocator.cs b/src/Velopack/Locators/OsxVelopackLocator.cs index 36fff19c..62d53409 100644 --- a/src/Velopack/Locators/OsxVelopackLocator.cs +++ b/src/Velopack/Locators/OsxVelopackLocator.cs @@ -15,28 +15,28 @@ namespace Velopack.Locators public class OsxVelopackLocator : VelopackLocator { /// - public override string AppId { get; } + public override string? AppId { get; } /// - public override string RootAppDir { get; } + public override string? RootAppDir { get; } /// - public override string UpdateExePath { get; } + public override string? UpdateExePath { get; } /// - public override SemanticVersion CurrentlyInstalledVersion { get; } + public override SemanticVersion? CurrentlyInstalledVersion { get; } /// - public override string AppContentDir => RootAppDir; + public override string? AppContentDir => RootAppDir; /// - public override string AppTempDir => CreateSubDirIfDoesNotExist(Utility.GetDefaultTempBaseDirectory(), AppId); + public override string? AppTempDir => CreateSubDirIfDoesNotExist(Utility.GetDefaultTempBaseDirectory(), AppId); /// - public override string PackagesDir => CreateSubDirIfDoesNotExist(AppTempDir, "packages"); + public override string? PackagesDir => CreateSubDirIfDoesNotExist(AppTempDir, "packages"); /// - public override string Channel { get; } + public override string? Channel { get; } /// /// Creates a new and auto-detects the diff --git a/src/Velopack/Locators/TestVelopackLocator.cs b/src/Velopack/Locators/TestVelopackLocator.cs index a6ceb52e..b887543f 100644 --- a/src/Velopack/Locators/TestVelopackLocator.cs +++ b/src/Velopack/Locators/TestVelopackLocator.cs @@ -14,7 +14,7 @@ namespace Velopack.Locators public class TestVelopackLocator : VelopackLocator { /// - public override string AppId { + public override string? AppId { get { if (_id == null) { throw new NotSupportedException("AppId is not supported in this test implementation."); @@ -24,7 +24,7 @@ namespace Velopack.Locators } /// - public override string RootAppDir { + public override string? RootAppDir { get { if (_root == null) { throw new NotSupportedException("RootAppDir is not supported in this test implementation."); @@ -34,7 +34,7 @@ namespace Velopack.Locators } /// - public override string PackagesDir { + public override string? PackagesDir { get { if (_packages == null) { throw new NotSupportedException("PackagesDir is not supported in this test implementation."); @@ -44,7 +44,7 @@ namespace Velopack.Locators } /// - public override string UpdateExePath { + public override string? UpdateExePath { get { if (_updatePath == null) { throw new NotSupportedException("UpdateExePath is not supported in this test implementation."); @@ -54,7 +54,7 @@ namespace Velopack.Locators } /// - public override SemanticVersion CurrentlyInstalledVersion { + public override SemanticVersion? CurrentlyInstalledVersion { get { if (_version == null) { throw new NotSupportedException("CurrentlyInstalledVersion is not supported in this test implementation."); @@ -64,7 +64,7 @@ namespace Velopack.Locators } /// - public override string AppContentDir { + public override string? AppContentDir { get { if (_appContent == null) { throw new NotSupportedException("AppContentDir is not supported in this test implementation."); @@ -74,29 +74,29 @@ namespace Velopack.Locators } /// - public override string Channel { + public override string? Channel { get { return _channel; } } - private readonly string _updatePath; - private readonly SemanticVersion _version; - private readonly string _packages; - private readonly string _id; - private readonly string _root; - private readonly string _appContent; - private readonly string _channel; + private readonly string? _updatePath; + private readonly SemanticVersion? _version; + private readonly string? _packages; + private readonly string? _id; + private readonly string? _root; + private readonly string? _appContent; + private readonly string? _channel; /// - public TestVelopackLocator(string appId, string version, string packagesDir, ILogger logger = null) + public TestVelopackLocator(string appId, string version, string packagesDir, ILogger? logger = null) : this(appId, version, packagesDir, null, null, null, null, logger) { } /// - public TestVelopackLocator(string appId, string version, string packagesDir, string appDir, - string rootDir, string updateExe, string channel = null, ILogger logger = null) + public TestVelopackLocator(string appId, string version, string packagesDir, string? appDir, + string? rootDir, string? updateExe, string? channel = null, ILogger? logger = null) : base(logger) { _id = appId; diff --git a/src/Velopack/Locators/VelopackLocator.cs b/src/Velopack/Locators/VelopackLocator.cs index 5d1dfca9..3aa310d4 100644 --- a/src/Velopack/Locators/VelopackLocator.cs +++ b/src/Velopack/Locators/VelopackLocator.cs @@ -15,7 +15,7 @@ namespace Velopack.Locators /// public abstract class VelopackLocator : IVelopackLocator { - private static VelopackLocator _current; + private static VelopackLocator? _current; /// /// Auto-detect the platform from the current operating system. @@ -40,29 +40,30 @@ namespace Velopack.Locators } /// - public abstract string AppId { get; } + public abstract string? AppId { get; } /// - public abstract string RootAppDir { get; } + public abstract string? RootAppDir { get; } /// - public abstract string PackagesDir { get; } + public abstract string? PackagesDir { get; } /// - public virtual string AppTempDir => CreateSubDirIfDoesNotExist(PackagesDir, "VelopackTemp"); + public virtual string? AppTempDir => CreateSubDirIfDoesNotExist(PackagesDir, "VelopackTemp"); /// - public abstract string UpdateExePath { get; } + public abstract string? UpdateExePath { get; } /// - public abstract string AppContentDir { get; } + public abstract string? AppContentDir { get; } /// - public abstract string Channel { get; } + public abstract string? Channel { get; } /// - public virtual string ThisExeRelativePath { + public virtual string? ThisExeRelativePath { get { + if (AppContentDir == null) return null; var path = VelopackRuntimeInfo.EntryExePath; if (path.StartsWith(AppContentDir, StringComparison.OrdinalIgnoreCase)) { return path.Substring(AppContentDir.Length + 1); @@ -73,13 +74,13 @@ namespace Velopack.Locators } /// - public abstract SemanticVersion CurrentlyInstalledVersion { get; } + public abstract SemanticVersion? CurrentlyInstalledVersion { get; } /// The log interface to use for diagnostic messages. protected ILogger Log { get; } /// - protected VelopackLocator(ILogger logger) + protected VelopackLocator(ILogger? logger) { Log = logger ?? NullLogger.Instance; } @@ -92,14 +93,16 @@ namespace Velopack.Locators return new List(0); var list = new List(); - foreach (var pkg in Directory.EnumerateFiles(PackagesDir, "*.nupkg")) { - try { - var asset = VelopackAsset.FromNupkg(pkg); - if (asset?.Version != null) { - list.Add(asset); + if (PackagesDir != null) { + foreach (var pkg in Directory.EnumerateFiles(PackagesDir, "*.nupkg")) { + try { + var asset = VelopackAsset.FromNupkg(pkg); + if (asset?.Version != null) { + list.Add(asset); + } + } catch (Exception ex) { + Log.Warn(ex, $"Error while reading local package '{pkg}'."); } - } catch (Exception ex) { - Log.Warn(ex, $"Error while reading local package '{pkg}'."); } } return list; @@ -110,7 +113,7 @@ namespace Velopack.Locators } /// - public VelopackAsset GetLatestLocalFullPackage() + public VelopackAsset? GetLatestLocalFullPackage() { return GetLocalPackages() .OrderByDescending(x => x.Version) @@ -122,7 +125,7 @@ namespace Velopack.Locators /// Given a base dir and a directory name, will create a new sub directory of that name. /// Will return null if baseDir is null, or if baseDir does not exist. /// - protected static string CreateSubDirIfDoesNotExist(string baseDir, string newDir) + protected static string? CreateSubDirIfDoesNotExist(string? baseDir, string? newDir) { if (String.IsNullOrEmpty(baseDir) || string.IsNullOrEmpty(newDir)) return null; var infoBase = new DirectoryInfo(baseDir); @@ -135,6 +138,7 @@ namespace Velopack.Locators /// public Guid? GetOrCreateStagedUserId() { + if (PackagesDir == null) return null; var stagedUserIdFile = Path.Combine(PackagesDir, ".betaId"); var ret = default(Guid); diff --git a/src/Velopack/Locators/WindowsVelopackLocator.cs b/src/Velopack/Locators/WindowsVelopackLocator.cs index 671cfa42..d5727fdd 100644 --- a/src/Velopack/Locators/WindowsVelopackLocator.cs +++ b/src/Velopack/Locators/WindowsVelopackLocator.cs @@ -14,25 +14,25 @@ namespace Velopack.Locators public class WindowsVelopackLocator : VelopackLocator { /// - public override string AppId { get; } + public override string? AppId { get; } /// - public override string RootAppDir { get; } + public override string? RootAppDir { get; } /// - public override string UpdateExePath { get; } + public override string? UpdateExePath { get; } /// - public override string AppContentDir { get; } + public override string? AppContentDir { get; } /// - public override SemanticVersion CurrentlyInstalledVersion { get; } + public override SemanticVersion? CurrentlyInstalledVersion { get; } /// - public override string PackagesDir => CreateSubDirIfDoesNotExist(RootAppDir, "packages"); + public override string? PackagesDir => CreateSubDirIfDoesNotExist(RootAppDir, "packages"); /// - public override string Channel { get; } + public override string? Channel { get; } /// public WindowsVelopackLocator(ILogger logger) : this(VelopackRuntimeInfo.EntryExePath, logger) @@ -55,7 +55,7 @@ namespace Velopack.Locators // directory which is NOT containing a sq.version, in which case we need to infer a lot of info. ourExePath = Path.GetFullPath(ourExePath); - var myDirPath = Path.GetDirectoryName(ourExePath); + string myDirPath = Path.GetDirectoryName(ourExePath)!; var myDirName = Path.GetFileName(myDirPath); var possibleUpdateExe = Path.GetFullPath(Path.Combine(myDirPath, "..\\Update.exe")); var ixCurrent = ourExePath.LastIndexOf("/current/", StringComparison.InvariantCultureIgnoreCase); diff --git a/src/Velopack/NuGet/IPackage.cs b/src/Velopack/NuGet/IPackage.cs deleted file mode 100644 index f843fd14..00000000 --- a/src/Velopack/NuGet/IPackage.cs +++ /dev/null @@ -1,25 +0,0 @@ -#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member -using System; -using System.Collections.Generic; -using NuGet.Versioning; - -namespace Velopack.NuGet -{ - public interface IPackage - { - string Id { get; } - string ProductName { get; } - string ProductDescription { get; } - string ProductCompany { get; } - string ProductCopyright { get; } - string Language { get; } - SemanticVersion Version { get; } - Uri ProjectUrl { get; } - string ReleaseNotes { get; } - Uri IconUrl { get; } - IEnumerable Tags { get; } - IEnumerable FrameworkAssemblies { get; } - IEnumerable DependencySets { get; } - IEnumerable RuntimeDependencies { get; } - } -} diff --git a/src/Velopack/NuGet/IPackageFile.cs b/src/Velopack/NuGet/IPackageFile.cs deleted file mode 100644 index 0e57c3e1..00000000 --- a/src/Velopack/NuGet/IPackageFile.cs +++ /dev/null @@ -1,15 +0,0 @@ -#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member -using System; - -namespace Velopack.NuGet -{ - public interface IPackageFile - { - Uri Key { get; } - string Path { get; } - string EffectivePath { get; } - string TargetFramework { get; } - bool IsLibFile(); - bool IsContentFile(); - } -} \ No newline at end of file diff --git a/src/Velopack/NuGet/IZipPackage.cs b/src/Velopack/NuGet/IZipPackage.cs deleted file mode 100644 index 3d831b4d..00000000 --- a/src/Velopack/NuGet/IZipPackage.cs +++ /dev/null @@ -1,11 +0,0 @@ -#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member -using System.Collections.Generic; - -namespace Velopack.NuGet -{ - public interface IZipPackage : IPackage - { - IEnumerable Frameworks { get; } - IEnumerable Files { get; } - } -} \ No newline at end of file diff --git a/src/Velopack/NuGet/NugetUtil.cs b/src/Velopack/NuGet/NugetUtil.cs index dada7689..55f4338a 100644 --- a/src/Velopack/NuGet/NugetUtil.cs +++ b/src/Velopack/NuGet/NugetUtil.cs @@ -1,4 +1,5 @@ -using System; +#nullable disable +using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; diff --git a/src/Velopack/NuGet/NuspecManifest.cs b/src/Velopack/NuGet/NuspecManifest.cs index 69ab7640..f5b574c8 100644 --- a/src/Velopack/NuGet/NuspecManifest.cs +++ b/src/Velopack/NuGet/NuspecManifest.cs @@ -8,34 +8,27 @@ using NuGet.Versioning; namespace Velopack.NuGet { - public class NuspecManifest : IPackage + public class NuspecManifest { - public string ProductName => Title ?? Id; - public string ProductDescription => Description ?? Summary ?? Title ?? Id; - public string ProductCompany => (Authors.Any() ? String.Join(", ", Authors) : Owners) ?? ProductName; - public string ProductCopyright => Copyright ?? "Copyright © " + DateTime.Now.Year.ToString() + " " + ProductCompany; - - //public string FilePath { get; private set; } - - public string Id { get; private set; } - public SemanticVersion Version { get; private set; } - public Uri ProjectUrl { get; private set; } - public string ReleaseNotes { get; private set; } - public string ReleaseNotesHtml { get; private set; } - public Uri IconUrl { get; private set; } - public string Language { get; private set; } - public IEnumerable Tags { get; private set; } = Enumerable.Empty(); + public string? ProductName => Title ?? Id; + public string? ProductDescription => Description ?? Summary ?? Title ?? Id; + public string? ProductCompany => (Authors.Any() ? String.Join(", ", Authors) : Owners) ?? ProductName; + public string? ProductCopyright => Copyright ?? "Copyright © " + DateTime.Now.Year.ToString() + " " + ProductCompany; + public string? Id { get; private set; } + public SemanticVersion? Version { get; private set; } + public Uri? ProjectUrl { get; private set; } + public string? ReleaseNotes { get; private set; } + public string? ReleaseNotesHtml { get; private set; } + public Uri? IconUrl { get; private set; } + public string? Language { get; private set; } + public string? Channel { get; private set; } + public string? Description { get; private set; } + public string? Owners { get; private set; } + public string? Title { get; private set; } + public string? Summary { get; private set; } + public string? Copyright { get; private set; } + public IEnumerable Authors { get; private set; } = Enumerable.Empty(); public IEnumerable RuntimeDependencies { get; private set; } = Enumerable.Empty(); - public IEnumerable FrameworkAssemblies { get; private set; } = Enumerable.Empty(); - public IEnumerable DependencySets { get; private set; } = Enumerable.Empty(); - public string Channel { get; private set; } - - protected string Description { get; private set; } - protected IEnumerable Authors { get; private set; } = Enumerable.Empty(); - protected string Owners { get; private set; } - protected string Title { get; private set; } - protected string Summary { get; private set; } - protected string Copyright { get; private set; } private static readonly string[] ExcludePaths = new[] { "_rels", "package" }; @@ -56,7 +49,7 @@ namespace Velopack.NuGet manifest = ParseFromFile(filePath); return true; } catch { - manifest = null; + manifest = null!; return false; } } @@ -65,14 +58,11 @@ namespace Velopack.NuGet { var document = NugetUtil.LoadSafe(manifestStream, ignoreWhiteSpace: true); - var metadataElement = document.Root.ElementsNoNamespace("metadata").FirstOrDefault(); - if (metadataElement == null) { - throw new InvalidDataException("Invalid nuspec xml. Required element 'metadata' missing."); - } - + var metadataElement = document.Root.ElementsNoNamespace("metadata").FirstOrDefault() + ?? throw new InvalidDataException("Invalid nuspec xml. Required element 'metadata' missing."); var allElements = new HashSet(); - XNode node = metadataElement.FirstNode; + XNode? node = metadataElement.FirstNode; while (node != null) { var element = node as XElement; if (element != null) { @@ -134,15 +124,6 @@ namespace Velopack.NuGet case "title": Title = value; break; - case "tags": - Tags = getCommaDelimitedValue(value); - break; - case "dependencies": - DependencySets = ReadDependencySets(element); - break; - case "frameworkAssemblies": - FrameworkAssemblies = ReadFrameworkAssemblies(element); - break; // === // the following metadata elements are added by velopack and are not @@ -159,70 +140,6 @@ namespace Velopack.NuGet } } - private List ReadFrameworkAssemblies(XElement frameworkElement) - { - if (!frameworkElement.HasElements) { - return new List(0); - } - - return (from element in frameworkElement.ElementsNoNamespace("frameworkAssembly") - let assemblyNameAttribute = element.Attribute("assemblyName") - where assemblyNameAttribute != null && !String.IsNullOrEmpty(assemblyNameAttribute.Value) - select new FrameworkAssemblyReference( - assemblyNameAttribute.Value.SafeTrim(), - ParseFrameworkNames(element.GetOptionalAttributeValue("targetFramework").SafeTrim())) - ).ToList(); - } - - private List ReadDependencySets(XElement dependenciesElement) - { - if (!dependenciesElement.HasElements) { - return new List(); - } - - // Disallow the element to contain both and - // child elements. Unfortunately, this cannot be enforced by XSD. - if (dependenciesElement.ElementsNoNamespace("dependency").Any() && - dependenciesElement.ElementsNoNamespace("group").Any()) { - throw new InvalidDataException("Manifest_DependenciesHasMixedElements"); - } - - var dependencies = ReadDependencies(dependenciesElement); - if (dependencies.Count > 0) { - // old format, is direct child of - var dependencySet = new PackageDependencySet(null, dependencies); - return new List { dependencySet }; - } else { - var groups = dependenciesElement.ElementsNoNamespace("group"); - return (from element in groups - let fx = ParseFrameworkNames(element.GetOptionalAttributeValue("targetFramework").SafeTrim()) - select new PackageDependencySet( - element.GetOptionalAttributeValue("targetFramework").SafeTrim(), - ReadDependencies(element))).ToList(); - } - } - - private List ReadDependencies(XElement containerElement) - { - // element is - return (from element in containerElement.ElementsNoNamespace("dependency") - let idElement = element.Attribute("id") - where idElement != null && !String.IsNullOrEmpty(idElement.Value) - select new PackageDependency( - idElement.Value.SafeTrim(), - element.GetOptionalAttributeValue("version").SafeTrim() - )).ToList(); - } - - private IEnumerable ParseFrameworkNames(string frameworkNames) - { - if (String.IsNullOrEmpty(frameworkNames)) { - return Enumerable.Empty(); - } - - return frameworkNames.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - } - protected bool IsPackageFile(string partPath) { if (Path.GetFileName(partPath).Equals(NugetUtil.ContentTypeFileName, StringComparison.OrdinalIgnoreCase)) @@ -231,7 +148,7 @@ namespace Velopack.NuGet if (Path.GetExtension(partPath).Equals(NugetUtil.ManifestExtension, StringComparison.OrdinalIgnoreCase)) return false; - string directory = Path.GetDirectoryName(partPath); + string directory = Path.GetDirectoryName(partPath)!; return !ExcludePaths.Any(p => directory.StartsWith(p, StringComparison.OrdinalIgnoreCase)); } } diff --git a/src/Velopack/NuGet/PackageDependency.cs b/src/Velopack/NuGet/PackageDependency.cs deleted file mode 100644 index f8c2a31b..00000000 --- a/src/Velopack/NuGet/PackageDependency.cs +++ /dev/null @@ -1,108 +0,0 @@ -#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Globalization; -using System.Linq; - -namespace Velopack.NuGet -{ - public class PackageDependency - { - public PackageDependency(string id) - : this(id, versionSpec: null) - { - } - - public PackageDependency(string id, string versionSpec) - { - if (String.IsNullOrEmpty(id)) { - throw new ArgumentException("Argument_Cannot_Be_Null_Or_Empty", "id"); - } - Id = id; - VersionSpec = versionSpec; - } - - public string Id { - get; - private set; - } - - public string VersionSpec { - get; - private set; - } - - public override string ToString() - { - if (VersionSpec == null) { - return Id; - } - - return Id + " " + VersionSpec; - } - } - - public class PackageDependencySet - { - private readonly string _targetFramework; - private readonly ReadOnlyCollection _dependencies; - - public PackageDependencySet(string targetFramework, IEnumerable dependencies) - { - if (dependencies == null) { - throw new ArgumentNullException("dependencies"); - } - - _targetFramework = targetFramework; - _dependencies = new ReadOnlyCollection(dependencies.ToList()); - } - - public string TargetFramework { - get { - return _targetFramework; - } - } - - public ICollection Dependencies { - get { - return _dependencies; - } - } - - public IEnumerable SupportedFrameworks { - get { - if (TargetFramework == null) { - yield break; - } - - yield return TargetFramework; - } - } - } - - public class FrameworkAssemblyReference - { - public FrameworkAssemblyReference(string assemblyName) - : this(assemblyName, Enumerable.Empty()) - { - } - - public FrameworkAssemblyReference(string assemblyName, IEnumerable supportedFrameworks) - { - if (String.IsNullOrEmpty(assemblyName)) { - throw new ArgumentException(String.Format(CultureInfo.CurrentCulture, "Argument_Cannot_Be_Null_Or_Empty", "assemblyName")); - } - - if (supportedFrameworks == null) { - throw new ArgumentNullException("supportedFrameworks"); - } - - AssemblyName = assemblyName; - SupportedFrameworks = supportedFrameworks; - } - - public string AssemblyName { get; private set; } - public IEnumerable SupportedFrameworks { get; private set; } - } -} \ No newline at end of file diff --git a/src/Velopack/NuGet/ZipPackage.cs b/src/Velopack/NuGet/ZipPackage.cs index acd32313..d0f45527 100644 --- a/src/Velopack/NuGet/ZipPackage.cs +++ b/src/Velopack/NuGet/ZipPackage.cs @@ -7,32 +7,27 @@ using System.Linq; namespace Velopack.NuGet { - public class ZipPackage : NuspecManifest, IZipPackage + public class ZipPackage : NuspecManifest { - public IEnumerable Frameworks { get; private set; } = Enumerable.Empty(); - public IEnumerable Files { get; private set; } = Enumerable.Empty(); - public byte[] UpdateExeBytes { get; private set; } + public byte[]? UpdateExeBytes { get; private set; } public string LoadedFromPath { get; private set; } - public ZipPackage(string filePath) : this(File.OpenRead(filePath)) + public ZipPackage(string filePath) { - LoadedFromPath = filePath; - } - - private ZipPackage(Stream zipStream, bool leaveOpen = false) - { - using var zip = new ZipArchive(zipStream, ZipArchiveMode.Read, leaveOpen); + using var zipStream = File.OpenRead(filePath); + using var zip = new ZipArchive(zipStream, ZipArchiveMode.Read, false); using var manifest = GetManifestEntry(zip).Open(); ReadManifest(manifest); + + LoadedFromPath = filePath; Files = GetPackageFiles(zip).ToArray(); - Frameworks = GetFrameworks(Files); - UpdateExeBytes = ReadFileToBytes(zip, f => f.FullName.EndsWith("Squirrel.exe")); + UpdateExeBytes = ReadFile(zip, f => f.FullName.EndsWith("Squirrel.exe")); } - protected byte[] ReadFileToBytes(ZipArchive archive, Func predicate) + protected byte[]? ReadFile(ZipArchive archive, Func predicate) { var f = archive.Entries.FirstOrDefault(predicate); if (f == null) @@ -68,15 +63,5 @@ namespace Velopack.NuGet where IsPackageFile(path) select new ZipPackageFile(uri); } - - private string[] GetFrameworks(IEnumerable files) - { - return FrameworkAssemblies - .SelectMany(f => f.SupportedFrameworks) - .Concat(files.Select(z => z.TargetFramework)) - .Where(f => f != null) - .Distinct() - .ToArray(); - } } } \ No newline at end of file diff --git a/src/Velopack/NuGet/ZipPackageFile.cs b/src/Velopack/NuGet/ZipPackageFile.cs index c4048347..73adc355 100644 --- a/src/Velopack/NuGet/ZipPackageFile.cs +++ b/src/Velopack/NuGet/ZipPackageFile.cs @@ -3,7 +3,7 @@ using System; namespace Velopack.NuGet { - public class ZipPackageFile : IPackageFile, IEquatable + public class ZipPackageFile : IEquatable { public Uri Key { get; } public string EffectivePath { get; } @@ -31,14 +31,14 @@ namespace Velopack.NuGet public override int GetHashCode() => Path.GetHashCode(); - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (obj is ZipPackageFile zpf) return Equals(zpf); return false; } - public bool Equals(ZipPackageFile other) + public bool Equals(ZipPackageFile? other) { if (other == null) return false; return Path.Equals(other.Path); diff --git a/src/Velopack/RID.cs b/src/Velopack/RID.cs index 99b2fa62..f64c329c 100644 --- a/src/Velopack/RID.cs +++ b/src/Velopack/RID.cs @@ -1,4 +1,5 @@ -#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#nullable disable +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member // https://github.com/dotnet/runtime/tree/main/src/libraries/Microsoft.NETCore.Platforms using System; diff --git a/src/Velopack/ReleaseEntry.cs b/src/Velopack/ReleaseEntry.cs index 44b4db55..a4bdad24 100644 --- a/src/Velopack/ReleaseEntry.cs +++ b/src/Velopack/ReleaseEntry.cs @@ -1,4 +1,5 @@ -#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#nullable disable +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -187,7 +188,7 @@ namespace Velopack public static ReleaseEntry FromVelopackAsset(VelopackAsset asset) { - return new ReleaseEntry(asset.SHA1, asset.FileName, asset.Size ?? 0); + return new ReleaseEntry(asset.SHA1, asset.FileName, asset.Size); } /// diff --git a/src/Velopack/Sources/GitBase.cs b/src/Velopack/Sources/GitBase.cs index 4a139c9d..552244fe 100644 --- a/src/Velopack/Sources/GitBase.cs +++ b/src/Velopack/Sources/GitBase.cs @@ -36,10 +36,10 @@ namespace Velopack.Sources /// /// The Bearer token used in the request. /// - protected virtual string Authorization => string.IsNullOrWhiteSpace(AccessToken) ? null : "Bearer " + AccessToken; + protected virtual string? Authorization => string.IsNullOrWhiteSpace(AccessToken) ? null : "Bearer " + AccessToken; /// - public GitBase(string repoUrl, string accessToken, bool prerelease, IFileDownloader downloader = null) + public GitBase(string repoUrl, string accessToken, bool prerelease, IFileDownloader? downloader = null) { RepoUri = new Uri(repoUrl); AccessToken = accessToken; @@ -61,7 +61,7 @@ namespace Velopack.Sources } /// - public virtual async Task GetReleaseFeed(ILogger logger, string channel, Guid? stagingId = null, VelopackAsset latestLocalRelease = null) + public virtual async Task GetReleaseFeed(ILogger logger, string channel, Guid? stagingId = null, VelopackAsset? latestLocalRelease = null) { var releases = await GetReleases(Prerelease).ConfigureAwait(false); if (releases == null || releases.Count() == 0) { diff --git a/src/Velopack/Sources/GithubSource.cs b/src/Velopack/Sources/GithubSource.cs index 286c1fb8..d8bbaf4e 100644 --- a/src/Velopack/Sources/GithubSource.cs +++ b/src/Velopack/Sources/GithubSource.cs @@ -13,7 +13,7 @@ namespace Velopack.Sources { /// The name of this release. [JsonPropertyName("name")] - public string Name { get; set; } + public string? Name { get; set; } /// True if this release is a prerelease. [JsonPropertyName("prerelease")] @@ -25,7 +25,7 @@ namespace Velopack.Sources /// A list of assets (files) uploaded to this release. [JsonPropertyName("assets")] - public GithubReleaseAsset[] Assets { get; set; } + public GithubReleaseAsset[] Assets { get; set; } = new GithubReleaseAsset[0]; } /// Describes a asset (file) uploaded to a GitHub release. @@ -36,7 +36,7 @@ namespace Velopack.Sources /// quota and return JSON unless the 'Accept' header is "application/octet-stream". /// [JsonPropertyName("url")] - public string Url { get; set; } + public string? Url { get; set; } /// /// The browser URL for this release asset. This does not use API quota, @@ -45,15 +45,15 @@ namespace Velopack.Sources /// be used with an appropriate access token. /// [JsonPropertyName("browser_download_url")] - public string BrowserDownloadUrl { get; set; } + public string? BrowserDownloadUrl { get; set; } /// The name of this release asset. [JsonPropertyName("name")] - public string Name { get; set; } + public string? Name { get; set; } /// The mime type of this release asset (as detected by GitHub). [JsonPropertyName("content_type")] - public string ContentType { get; set; } + public string? ContentType { get; set; } } /// @@ -78,7 +78,7 @@ namespace Velopack.Sources /// /// The file downloader used to perform HTTP requests. /// - public GithubSource(string repoUrl, string accessToken, bool prerelease, IFileDownloader downloader = null) + public GithubSource(string repoUrl, string accessToken, bool prerelease, IFileDownloader? downloader = null) : base(repoUrl, accessToken, prerelease, downloader) { } @@ -94,6 +94,7 @@ namespace Velopack.Sources var getReleasesUri = new Uri(baseUri, releasesPath); var response = await Downloader.DownloadString(getReleasesUri.ToString(), Authorization, "application/vnd.github.v3+json").ConfigureAwait(false); var releases = SimpleJson.DeserializeObject>(response); + if (releases == null) return new GithubRelease[0]; return releases.OrderByDescending(d => d.PublishedAt).Where(x => includePrereleases || !x.Prerelease).ToArray(); } @@ -104,22 +105,24 @@ namespace Velopack.Sources throw new ArgumentException($"No assets found in Github Release '{release.Name}'."); } - IEnumerable allReleasesFiles = release.Assets.Where(a => a.Name.Equals(assetName, StringComparison.InvariantCultureIgnoreCase)); + IEnumerable allReleasesFiles = release.Assets.Where(a => a.Name?.Equals(assetName, StringComparison.InvariantCultureIgnoreCase) == true); if (allReleasesFiles == null || allReleasesFiles.Count() == 0) { throw new ArgumentException($"Could not find asset called '{assetName}' in Github Release '{release.Name}'."); } var asset = allReleasesFiles.First(); - if (String.IsNullOrWhiteSpace(AccessToken)) { + if (String.IsNullOrWhiteSpace(AccessToken) && asset.BrowserDownloadUrl != null) { // if no AccessToken provided, we use the BrowserDownloadUrl which does not // count towards the "unauthenticated api request" limit of 60 per hour per IP. return asset.BrowserDownloadUrl; - } else { + } else if (asset.Url != null) { // otherwise, we use the regular asset url, which will allow us to retrieve // assets from private repositories // https://docs.github.com/en/rest/reference/releases#get-a-release-asset return asset.Url; + } else { + throw new ArgumentException("Could not find a valid asset url for the specified asset."); } } diff --git a/src/Velopack/Sources/GitlabSource.cs b/src/Velopack/Sources/GitlabSource.cs index 64a8bc2d..a0c2e1ab 100644 --- a/src/Velopack/Sources/GitlabSource.cs +++ b/src/Velopack/Sources/GitlabSource.cs @@ -18,7 +18,7 @@ namespace Velopack.Sources /// The name of the release. /// [JsonPropertyName("name")] - public string Name { get; set; } + public string? Name { get; set; } /// /// True if this is intended for an upcoming release. @@ -30,13 +30,13 @@ namespace Velopack.Sources /// The date which this release was published publically. /// [JsonPropertyName("released_at")] - public DateTime ReleasedAt { get; set; } + public DateTime? ReleasedAt { get; set; } /// /// A container for the assets (files) uploaded to this release. /// [JsonPropertyName("assets")] - public GitlabReleaseAsset Assets { get; set; } + public GitlabReleaseAsset? Assets { get; set; } } /// @@ -54,7 +54,7 @@ namespace Velopack.Sources /// A list of asset (file) links. /// [JsonPropertyName("links")] - public GitlabReleaseLink[] Links { get; set; } + public GitlabReleaseLink[] Links { get; set; } = new GitlabReleaseLink[0]; } /// @@ -66,13 +66,13 @@ namespace Velopack.Sources /// Name of the asset (file) linked. /// [JsonPropertyName("name")] - public string Name { get; set; } + public string? Name { get; set; } /// /// The url for the asset. This make use of the Gitlab API. /// [JsonPropertyName("url")] - public string Url { get; set; } + public string? Url { get; set; } /// /// A direct url to the asset, via a traditional URl. @@ -80,14 +80,14 @@ namespace Velopack.Sources /// This links directly to the raw asset (file). /// [JsonPropertyName("direct_asset_url")] - public string DirectAssetUrl { get; set; } + public string? DirectAssetUrl { get; set; } /// /// The category type that the asset is listed under. /// Options: 'Package', 'Image', 'Runbook', 'Other' /// [JsonPropertyName("link_type")] - public string Type { get; set; } + public string? Type { get; set; } } /// @@ -111,7 +111,7 @@ namespace Velopack.Sources /// /// The file downloader used to perform HTTP requests. /// - public GitlabSource(string repoUrl, string accessToken, bool upcomingRelease, IFileDownloader downloader = null) + public GitlabSource(string repoUrl, string accessToken, bool upcomingRelease, IFileDownloader? downloader = null) : base(repoUrl, accessToken, upcomingRelease, downloader) { } @@ -128,16 +128,18 @@ namespace Velopack.Sources throw new ArgumentException($"No assets found in Gitlab Release '{release.Name}'."); } - GitlabReleaseLink packageFile = - release.Assets.Links.FirstOrDefault(a => a.Name.Equals(assetName, StringComparison.InvariantCultureIgnoreCase)); + GitlabReleaseLink? packageFile = + release.Assets.Links.FirstOrDefault(a => a.Name?.Equals(assetName, StringComparison.InvariantCultureIgnoreCase) == true); if (packageFile == null) { throw new ArgumentException($"Could not find asset called '{assetName}' in GitLab Release '{release.Name}'."); } - if (String.IsNullOrWhiteSpace(AccessToken)) { + if (String.IsNullOrWhiteSpace(AccessToken) && packageFile.DirectAssetUrl != null) { return packageFile.DirectAssetUrl; - } else { + } else if (packageFile.Url != null) { return packageFile.Url; + } else { + throw new Exception($"Could not find a valid URL for asset '{assetName}' in GitLab Release '{release.Name}'."); } } @@ -154,6 +156,7 @@ namespace Velopack.Sources var getReleasesUri = new Uri(baseUri, releasesPath); var response = await Downloader.DownloadString(getReleasesUri.ToString(), Authorization).ConfigureAwait(false); var releases = SimpleJson.DeserializeObject>(response); + if (releases == null) return new GitlabRelease[0]; return releases.OrderByDescending(d => d.ReleasedAt).Where(x => includePrereleases || !x.UpcomingRelease).ToArray(); } } diff --git a/src/Velopack/Sources/HttpClientFileDownloader.cs b/src/Velopack/Sources/HttpClientFileDownloader.cs index 7a841f47..7c3b057a 100644 --- a/src/Velopack/Sources/HttpClientFileDownloader.cs +++ b/src/Velopack/Sources/HttpClientFileDownloader.cs @@ -18,7 +18,7 @@ namespace Velopack.Sources public static ProductInfoHeaderValue UserAgent => new("Velopack", VelopackRuntimeInfo.VelopackNugetVersion.ToFullString()); /// - public virtual async Task DownloadFile(string url, string targetFile, Action progress, string authorization, string accept) + public virtual async Task DownloadFile(string url, string targetFile, Action progress, string? authorization, string? accept) { using var client = CreateHttpClient(authorization, accept); try { @@ -35,7 +35,7 @@ namespace Velopack.Sources } /// - public virtual async Task DownloadBytes(string url, string authorization, string accept) + public virtual async Task DownloadBytes(string url, string? authorization, string? accept) { using var client = CreateHttpClient(authorization, accept); try { @@ -48,7 +48,7 @@ namespace Velopack.Sources } /// - public virtual async Task DownloadString(string url, string authorization, string accept) + public virtual async Task DownloadString(string url, string? authorization, string? accept) { using var client = CreateHttpClient(authorization, accept); try { @@ -64,7 +64,7 @@ namespace Velopack.Sources /// Asynchronously downloads a remote url to the specified destination stream while /// providing progress updates. /// - protected virtual async Task DownloadToStreamInternal(HttpClient client, string requestUri, Stream destination, Action progress = null, CancellationToken cancellationToken = default) + protected virtual async Task DownloadToStreamInternal(HttpClient client, string requestUri, Stream destination, Action? progress = null, CancellationToken cancellationToken = default) { // https://stackoverflow.com/a/46497896/184746 // Get the http headers first to examine the content length @@ -119,7 +119,7 @@ namespace Velopack.Sources /// /// Creates a new for every request. /// - protected virtual HttpClient CreateHttpClient(string authorization, string accept) + protected virtual HttpClient CreateHttpClient(string? authorization, string? accept) { var client = new HttpClient(CreateHttpClientHandler(), true); client.DefaultRequestHeaders.UserAgent.Add(UserAgent); diff --git a/src/Velopack/Sources/IFileDownloader.cs b/src/Velopack/Sources/IFileDownloader.cs index 882ec3d2..56dbb404 100644 --- a/src/Velopack/Sources/IFileDownloader.cs +++ b/src/Velopack/Sources/IFileDownloader.cs @@ -24,16 +24,16 @@ namespace Velopack.Sources /// /// Text to be sent in the 'Accept' header of the request. /// - Task DownloadFile(string url, string targetFile, Action progress, string authorization = null, string accept = null); + Task DownloadFile(string url, string targetFile, Action progress, string? authorization = null, string? accept = null); /// /// Returns a byte array containing the contents of the file at the specified url /// - Task DownloadBytes(string url, string authorization = null, string accept = null); + Task DownloadBytes(string url, string? authorization = null, string? accept = null); /// /// Returns a string containing the contents of the specified url /// - Task DownloadString(string url, string authorization = null, string accept = null); + Task DownloadString(string url, string? authorization = null, string? accept = null); } } diff --git a/src/Velopack/Sources/IUpdateSource.cs b/src/Velopack/Sources/IUpdateSource.cs index 7576bcb6..7b5a4ff1 100644 --- a/src/Velopack/Sources/IUpdateSource.cs +++ b/src/Velopack/Sources/IUpdateSource.cs @@ -27,7 +27,7 @@ namespace Velopack.Sources /// The logger to use for any diagnostic messages. /// An array of objects that are available for download /// and are applicable to this user. - Task GetReleaseFeed(ILogger logger, string channel, Guid? stagingId = null, VelopackAsset latestLocalRelease = null); + Task GetReleaseFeed(ILogger logger, string channel, Guid? stagingId = null, VelopackAsset? latestLocalRelease = null); /// /// Download the specified to the provided local file path. diff --git a/src/Velopack/Sources/SimpleFileSource.cs b/src/Velopack/Sources/SimpleFileSource.cs index 813f6ac0..304c7994 100644 --- a/src/Velopack/Sources/SimpleFileSource.cs +++ b/src/Velopack/Sources/SimpleFileSource.cs @@ -24,7 +24,7 @@ namespace Velopack.Sources } /// - public Task GetReleaseFeed(ILogger logger, string channel, Guid? stagingId = null, VelopackAsset latestLocalRelease = null) + public Task GetReleaseFeed(ILogger logger, string channel, Guid? stagingId = null, VelopackAsset? latestLocalRelease = null) { if (!BaseDirectory.Exists) { logger.Error($"The local update directory '{BaseDirectory.FullName}' does not exist."); diff --git a/src/Velopack/Sources/SimpleWebSource.cs b/src/Velopack/Sources/SimpleWebSource.cs index f18378a0..ade77d13 100644 --- a/src/Velopack/Sources/SimpleWebSource.cs +++ b/src/Velopack/Sources/SimpleWebSource.cs @@ -21,19 +21,19 @@ namespace Velopack.Sources public virtual IFileDownloader Downloader { get; } /// - public SimpleWebSource(string baseUrl, IFileDownloader downloader = null) + public SimpleWebSource(string baseUrl, IFileDownloader? downloader = null) : this(new Uri(baseUrl), downloader) { } /// - public SimpleWebSource(Uri baseUri, IFileDownloader downloader = null) + public SimpleWebSource(Uri baseUri, IFileDownloader? downloader = null) { BaseUri = baseUri; Downloader = downloader ?? Utility.CreateDefaultDownloader(); } /// - public async Task GetReleaseFeed(ILogger logger, string channel = null, Guid? stagingId = null, VelopackAsset latestLocalRelease = null) + public async Task GetReleaseFeed(ILogger logger, string channel, Guid? stagingId = null, VelopackAsset? latestLocalRelease = null) { var uri = Utility.AppendPathToUri(BaseUri, Utility.GetVeloReleaseIndexName(channel)); var args = new Dictionary(); diff --git a/src/Velopack/UpdateExe.cs b/src/Velopack/UpdateExe.cs new file mode 100644 index 00000000..80defab8 --- /dev/null +++ b/src/Velopack/UpdateExe.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Threading; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Velopack.Locators; + +namespace Velopack +{ + /// + /// A static helper class to assist in running Update.exe CLI commands. You probably should not invoke this directly, + /// instead you should use the relevant methods on . For example: + /// , or . + /// + public static class UpdateExe + { + /// + /// Runs Update.exe in the current working directory to apply updates, optionally restarting the application. + /// + /// If true, no dialogs will be shown during the update process. This could result + /// in an update failing to install, such as when we need to ask the user for permission to install + /// a new framework dependency. + /// If true, restarts the application after updates are applied (or if they failed) + /// The locator to use to find the path to Update.exe and the packages directory. + /// The arguments to pass to the application when it is restarted. + /// The logger to use for diagnostic messages + /// Thrown if Update.exe does not initialize properly. + public static void Apply(IVelopackLocator locator, bool silent, bool restart, string[]? restartArgs, ILogger? logger = null) + { + logger ??= NullLogger.Instance; + var psi = new ProcessStartInfo() { + CreateNoWindow = true, + FileName = locator.UpdateExePath, + WorkingDirectory = Path.GetDirectoryName(locator.UpdateExePath), + }; + + var args = new List(); + if (silent) args.Add("--silent"); + args.Add("apply"); + args.Add("--wait"); + + var entry = locator.GetLatestLocalFullPackage(); + if (entry != null && locator.PackagesDir != null) { + var pkg = Path.Combine(locator.PackagesDir, entry.FileName); + if (File.Exists(pkg)) { + args.Add("--package"); + args.Add(pkg); + } + } + + if (restart) args.Add("--restart"); + if (restart && restartArgs != null && restartArgs.Length > 0) { + args.Add("--"); + foreach (var a in restartArgs) { + args.Add(a); + } + } + + psi.AppendArgumentListSafe(args, out var debugArgs); + logger.Debug($"Restarting app to apply updates. Running: {psi.FileName} {debugArgs}"); + + var p = Process.Start(psi); + Thread.Sleep(300); + if (p == null) { + throw new Exception("Failed to launch Update.exe process."); + } + if (p.HasExited) { + throw new Exception($"Update.exe process exited too soon ({p.ExitCode})."); + } + logger.Info("Update.exe apply triggered successfully."); + } + } +} diff --git a/src/Velopack/UpdateInfo.cs b/src/Velopack/UpdateInfo.cs index 36576f3a..f2dc53d1 100644 --- a/src/Velopack/UpdateInfo.cs +++ b/src/Velopack/UpdateInfo.cs @@ -14,7 +14,7 @@ /// The base release that we are to apply delta updates from. If null, we can try doing a delta update from /// the currently installed version. /// - public VelopackAsset BaseRelease { get; } + public VelopackAsset? BaseRelease { get; } /// /// The list of delta versions between the current version and . @@ -25,28 +25,11 @@ /// /// Create a new instance of /// - public UpdateInfo(VelopackAsset targetRelease, VelopackAsset deltaBaseRelease = null, VelopackAsset[] deltasToTarget = null) + public UpdateInfo(VelopackAsset targetRelease, VelopackAsset? deltaBaseRelease = null, VelopackAsset[]? deltasToTarget = null) { TargetFullRelease = targetRelease; BaseRelease = deltaBaseRelease; DeltasToTarget = deltasToTarget ?? new VelopackAsset[0]; } - - // /// - // /// Retrieves all the release notes for pending packages (ie. ) - // /// - // public Dictionary FetchReleaseNotes(ReleaseNotesFormat format) - // { - // return ReleasesToApply - // .SelectMany(x => { - // try { - // var releaseNotes = x.GetReleaseNotes(PackageDirectory, format); - // return EnumerableExtensions.Return(Tuple.Create(x, releaseNotes)); - // } catch (Exception ex) { - // return Enumerable.Empty>(); - // } - // }) - // .ToDictionary(k => k.Item1, v => v.Item2); - // } } } diff --git a/src/Velopack/UpdateManager.cs b/src/Velopack/UpdateManager.cs index 41f50351..a5012c29 100644 --- a/src/Velopack/UpdateManager.cs +++ b/src/Velopack/UpdateManager.cs @@ -21,23 +21,23 @@ namespace Velopack public class UpdateManager { /// The currently installed application Id. This would be what you set when you create your release. - public virtual string AppId => Locator.AppId; + public virtual string? AppId => Locator.AppId; /// True if this application is currently installed, and is able to download/check for updates. public virtual bool IsInstalled => Locator.CurrentlyInstalledVersion != null; - /// True if there is a local update prepared that requires a call to to be applied. + /// True if there is a local update prepared that requires a call to to be applied. public virtual bool IsUpdatePendingRestart { get { var latestLocal = Locator.GetLatestLocalFullPackage(); - if (latestLocal != null && latestLocal.Version > CurrentVersion) + if (latestLocal != null && CurrentVersion != null && latestLocal.Version > CurrentVersion) return true; return false; } } /// The currently installed app version when you created your release. Null if this is not a currently installed app. - public virtual SemanticVersion CurrentVersion => Locator.CurrentlyInstalledVersion; + public virtual SemanticVersion? CurrentVersion => Locator.CurrentlyInstalledVersion; /// The update source to use when checking for/downloading updates. protected IUpdateSource Source { get; } @@ -60,7 +60,7 @@ namespace Velopack /// it will be cached and used again. /// This should usually be left null. Providing an allows you to mock up certain application paths. /// For example, if you wanted to test that updates are working in a unit test, you could provide an instance of . - public UpdateManager(string urlOrPath, string channel = null, ILogger logger = null, IVelopackLocator locator = null) + public UpdateManager(string urlOrPath, string? channel = null, ILogger? logger = null, IVelopackLocator? locator = null) : this(CreateSimpleSource(urlOrPath), channel, logger, locator) { } @@ -75,24 +75,19 @@ namespace Velopack /// it will be cached and used again. /// This should usually be left null. Providing an allows you to mock up certain application paths. /// For example, if you wanted to test that updates are working in a unit test, you could provide an instance of . - public UpdateManager(IUpdateSource source, string channel = null, ILogger logger = null, IVelopackLocator locator = null) - : this(logger, locator) + public UpdateManager(IUpdateSource source, string? channel = null, ILogger? logger = null, IVelopackLocator? locator = null) { if (source == null) { throw new ArgumentNullException(nameof(source)); } Source = source; - Channel = channel; - } - - internal UpdateManager(ILogger logger, IVelopackLocator locator) - { Log = logger ?? VelopackApp.DefaultLogger ?? NullLogger.Instance; Locator = locator ?? VelopackLocator.GetDefault(Log); + Channel = channel ?? Locator.Channel ?? VelopackRuntimeInfo.SystemOs.GetOsShortName(); } /// - public UpdateInfo CheckForUpdates() + public UpdateInfo? CheckForUpdates() { return CheckForUpdatesAsync() .ConfigureAwait(false).GetAwaiter().GetResult(); @@ -104,16 +99,15 @@ namespace Velopack /// /// An optional cancellation token if you wish to stop this operation. /// Null if no updates, otherwise containing the version of the latest update available. - public virtual async Task CheckForUpdatesAsync(CancellationToken cancelToken = default) + public virtual async Task CheckForUpdatesAsync(CancellationToken cancelToken = default) { EnsureInstalled(); - var installedVer = CurrentVersion; + var installedVer = CurrentVersion!; var betaId = Locator.GetOrCreateStagedUserId(); var latestLocalFull = Locator.GetLatestLocalFullPackage(); Log.Debug("Retrieving latest release feed."); - var channel = Channel ?? Locator.Channel ?? VelopackRuntimeInfo.SystemOs.GetOsShortName(); - var feedObj = await Source.GetReleaseFeed(Log, channel, betaId, latestLocalFull).ConfigureAwait(false); + var feedObj = await Source.GetReleaseFeed(Log, Channel, betaId, latestLocalFull).ConfigureAwait(false); var feed = feedObj.Assets; var latestRemoteFull = feed.Where(r => r.Type == VelopackAssetType.Full).MaxBy(x => x.Version).FirstOrDefault(); @@ -137,7 +131,7 @@ namespace Velopack // if we have a local full release, we try to apply delta's from that version to target version. // if we do not have a local release, we try to apply delta's to a copy of the current installed app files. - SemanticVersion deltaFromVer = null; + SemanticVersion? deltaFromVer = null; if (latestLocalFull != null) { if (latestLocalFull.Version != installedVer) { Log.Warn($"The current running version is {installedVer}, however the latest available local full .nupkg " + @@ -155,7 +149,7 @@ namespace Velopack } /// - public void DownloadUpdates(UpdateInfo updates, Action progress = null, bool ignoreDeltas = false) + public void DownloadUpdates(UpdateInfo updates, Action? progress = null, bool ignoreDeltas = false) { DownloadUpdatesAsync(updates, progress, ignoreDeltas) .ConfigureAwait(false).GetAwaiter().GetResult(); @@ -172,9 +166,8 @@ namespace Velopack /// Whether to attempt downloading delta's or skip to full package download. /// An optional cancellation token if you wish to stop this operation. public virtual async Task DownloadUpdatesAsync( - UpdateInfo updates, Action progress = null, bool ignoreDeltas = false, CancellationToken cancelToken = default) + UpdateInfo updates, Action? 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 @@ -189,7 +182,11 @@ namespace Velopack } } - var targetRelease = updates?.TargetFullRelease; + if (updates == null) { + throw new ArgumentNullException(nameof(updates)); + } + + var targetRelease = updates.TargetFullRelease; if (targetRelease == null) { throw new ArgumentException("Must pass a valid UpdateInfo object with a non-null TargetFullRelease", nameof(updates)); } @@ -197,7 +194,10 @@ namespace Velopack EnsureInstalled(); using var _mut = AcquireUpdateLock(); - var completeFile = Path.Combine(Locator.PackagesDir, targetRelease.FileName); + var appTempDir = Locator.AppTempDir!; + var appPackageDir = Locator.PackagesDir!; + + var completeFile = Path.Combine(appPackageDir, targetRelease.FileName); var incompleteFile = completeFile + ".partial"; try { @@ -214,7 +214,7 @@ namespace Velopack var deltasCount = updates.DeltasToTarget.Count(); try { - if (deltasCount > 0) { + if (updates.BaseRelease?.FileName != null && deltasCount > 0) { if (ignoreDeltas) { Log.Info("Ignoring delta updates (ignoreDeltas parameter)"); } else { @@ -222,16 +222,12 @@ namespace Velopack Log.Info($"There are too many delta's ({deltasCount} > 10) or the sum of their size ({deltasSize} > {targetRelease.Size}) is too large. " + $"Only full update will be available."); } else { - using var _1 = Utility.GetTempDirectory(out var deltaStagingDir, Locator.AppTempDir); - if (updates.BaseRelease?.FileName != null) { - string basePackagePath = Path.Combine(Locator.PackagesDir, updates.BaseRelease.FileName); - if (!File.Exists(basePackagePath)) - throw new Exception($"Unable to find base package {basePackagePath} for delta update."); - EasyZip.ExtractZipToDirectory(Log, basePackagePath, deltaStagingDir); - } else { - Log.Warn("No base package available. Attempting delta update using application files."); - Utility.CopyFiles(Locator.AppContentDir, deltaStagingDir); - } + using var _1 = Utility.GetTempDirectory(out var deltaStagingDir, appTempDir); + string basePackagePath = Path.Combine(appPackageDir, updates.BaseRelease.FileName); + if (!File.Exists(basePackagePath)) + throw new Exception($"Unable to find base package {basePackagePath} for delta update."); + EasyZip.ExtractZipToDirectory(Log, basePackagePath, deltaStagingDir); + reportProgress(10); await DownloadAndApplyDeltaUpdates(deltaStagingDir, updates, x => reportProgress(Utility.CalculateProgress(x, 10, 80))) .ConfigureAwait(false); @@ -268,7 +264,7 @@ namespace Velopack } finally { if (VelopackRuntimeInfo.IsWindows) { try { - var updateExe = Locator.UpdateExePath; + var updateExe = Locator.UpdateExePath!; Log.Info("Extracting new Update.exe to " + updateExe); var zip = new ZipPackage(completeFile); @@ -291,24 +287,16 @@ namespace Velopack } /// - /// This will exit your app immediately and then apply updates. - /// - /// - public void ApplyUpdatesAndExit(bool silent = false) - { - RunApplyUpdates(silent, false, null); - Environment.Exit(0); - } - - /// - /// This will exit your app immediately, apply updates, and relaunch the app using the specified restart arguments. - /// If you need to save state or clean up, you should do that before calling this method. The user may be prompted - /// during the update, if the update requires additional frameworks to be installed etc. + /// This will exit your app immediately, apply updates, and then optionally relaunch the app using the specified + /// restart arguments. If you need to save state or clean up, you should do that before calling this method. + /// The user may be prompted during the update, if the update requires additional frameworks to be installed etc. + /// You can check if there are pending updates by checking . /// + /// Whether Velopack should restart the app after the updates have been applied. /// The arguments to pass to the application when it is restarted. - public void ApplyUpdatesAndRestart(string[] restartArgs = null) + public void ApplyPendingUpdate(bool restart = true, string[]? restartArgs = null) { - RunApplyUpdates(false, true, restartArgs); + WaitExitThenApplyPendingUpdate(restart, restartArgs); Environment.Exit(0); } @@ -316,81 +304,35 @@ namespace Velopack /// This will launch the Velopack updater and tell it to wait for this program to exit gracefully. /// You should then clean up any state and exit your app. The updater will apply updates and then /// optionally restart your app. The updater will only wait for 60 seconds before giving up. + /// You can check if there are pending updates by checking . /// - public void QueueApplyAndWait(bool restart = true, string[] restartArgs = null) - { - RunApplyUpdates(false, restart, restartArgs); - } - - /// - /// Runs Update.exe in the current working directory to apply updates, optionally restarting the application. - /// - /// If true, no dialogs will be shown during the update process. This could result - /// in an update failing to install, such as when we need to ask the user for permission to install - /// a new framework dependency. - /// If true, restarts the application after updates are applied (or if they failed) + /// Whether Velopack should restart the app after the updates have been applied. /// The arguments to pass to the application when it is restarted. - /// - protected virtual void RunApplyUpdates(bool silent, bool restart, string[] restartArgs) + public void WaitExitThenApplyPendingUpdate(bool restart = true, string[]? restartArgs = null) { - var psi = new ProcessStartInfo() { - CreateNoWindow = true, - FileName = Locator.UpdateExePath, - WorkingDirectory = Path.GetDirectoryName(Locator.UpdateExePath), - }; - - var args = new List(); - if (silent) args.Add("--silent"); - args.Add("apply"); - args.Add("--wait"); - - var entry = Locator.GetLatestLocalFullPackage(); - if (entry != null) { - var pkg = Path.Combine(Locator.PackagesDir, entry.FileName); - if (File.Exists(pkg)) { - args.Add("--package"); - args.Add(pkg); - } - } - - if (restart) args.Add("--restart"); - if (restart && restartArgs != null && restartArgs.Length > 0) { - args.Add("--"); - foreach (var a in restartArgs) { - args.Add(a); - } - } - - psi.AppendArgumentListSafe(args, out var debugArgs); - Log.Debug($"Restarting app to apply updates. Running: {psi.FileName} {debugArgs}"); - - var p = Process.Start(psi); - Thread.Sleep(300); - if (p == null) { - throw new Exception("Failed to launch Update.exe process."); - } - if (p.HasExited) { - throw new Exception($"Update.exe process exited too soon ({p.ExitCode})."); - } - Log.Info("Update.exe apply triggered successfully."); + UpdateExe.Apply(Locator, false, restart, restartArgs, Log); } /// - /// Given a folder containing the extracted base package, and a list of delta updates, downloads and applies the delta updates to the base package. + /// Given a folder containing the extracted base package, and a list of delta updates, downloads and applies the + /// delta updates to the base package. /// /// A folder containing the application files to apply the delta's to. /// An update object containing one or more delta's /// A callback reporting process of delta application progress (from 0-100). protected virtual async Task DownloadAndApplyDeltaUpdates(string extractedBasePackage, UpdateInfo updates, Action progress) { - var packagesDirectory = Locator.PackagesDir; var releasesToDownload = updates.DeltasToTarget.OrderBy(d => d.Version).ToArray(); + var appTempDir = Locator.AppTempDir!; + var appPackageDir = Locator.PackagesDir!; + var updateExe = Locator.UpdateExePath!; + // downloading accounts for 0%-50% of progress double current = 0; double toIncrement = 100.0 / releasesToDownload.Count(); await releasesToDownload.ForEachAsync(async x => { - var targetFile = Path.Combine(packagesDirectory, x.FileName); + var targetFile = Path.Combine(appPackageDir, x.FileName); double component = 0; Log.Debug($"Downloading delta version {x.Version}"); await Source.DownloadReleaseEntry(Log, x, targetFile, p => { @@ -409,11 +351,11 @@ namespace Velopack // applying deltas accounts for 50%-100% of progress double progressStepSize = 100d / releasesToDownload.Length; - var builder = new DeltaUpdateExe(Log, Locator.AppTempDir, Locator.UpdateExePath); + var builder = new DeltaUpdateExe(Log, appTempDir, updateExe); for (var i = 0; i < releasesToDownload.Length; i++) { var rel = releasesToDownload[i]; double baseProgress = i * progressStepSize; - var packageFile = Path.Combine(packagesDirectory, rel.FileName); + var packageFile = Path.Combine(appPackageDir, rel.FileName); builder.ApplyDeltaPackageFast(extractedBasePackage, packageFile, x => { var progressOfStep = (int) (baseProgress + (progressStepSize * (x / 100d))); progress(Utility.CalculateProgress(progressOfStep, 50, 100)); @@ -429,11 +371,12 @@ namespace Velopack protected void CleanIncompleteAndDeltaPackages() { try { + var appPackageDir = Locator.PackagesDir!; Log.Info("Cleaning up incomplete and delta packages from packages directory."); foreach (var l in Locator.GetLocalPackages()) { if (l.Type == VelopackAssetType.Delta) { try { - var pkgPath = Path.Combine(Locator.PackagesDir, l.FileName); + var pkgPath = Path.Combine(appPackageDir, l.FileName); File.Delete(pkgPath); Log.Trace(pkgPath + " deleted."); } catch (Exception ex) { @@ -442,7 +385,7 @@ namespace Velopack } } - foreach (var l in Directory.EnumerateFiles(Locator.PackagesDir, "*.partial").ToArray()) { + foreach (var l in Directory.EnumerateFiles(appPackageDir, "*.partial").ToArray()) { try { File.Delete(l); Log.Trace(l + " deleted."); @@ -460,10 +403,10 @@ namespace Velopack /// /// The entry to check /// Optional file path, if not specified the package will be loaded from %pkgdir%/release.OriginalFilename. - protected internal virtual void VerifyPackageChecksum(VelopackAsset release, string filePathOverride = null) + protected internal virtual void VerifyPackageChecksum(VelopackAsset release, string? filePathOverride = null) { var targetPackage = filePathOverride == null - ? new FileInfo(Path.Combine(Locator.PackagesDir, release.FileName)) + ? new FileInfo(Path.Combine(Locator.PackagesDir!, release.FileName)) : new FileInfo(filePathOverride); if (!targetPackage.Exists) { @@ -496,7 +439,7 @@ namespace Velopack { var mutexId = $"velopack-{AppId}"; bool created = false; - Mutex mutex = null; + Mutex? mutex = null; try { mutex = new Mutex(false, mutexId, out created); } catch (Exception ex) { diff --git a/src/Velopack/Velopack.csproj b/src/Velopack/Velopack.csproj index 786b35d7..7276a6c3 100644 --- a/src/Velopack/Velopack.csproj +++ b/src/Velopack/Velopack.csproj @@ -3,6 +3,7 @@ net462;net48;netstandard2.0;net6.0;net8.0 + enable true true false diff --git a/src/Velopack/VelopackApp.cs b/src/Velopack/VelopackApp.cs index 9ccd602c..10a7947f 100644 --- a/src/Velopack/VelopackApp.cs +++ b/src/Velopack/VelopackApp.cs @@ -24,14 +24,15 @@ namespace Velopack { internal static ILogger DefaultLogger { get; private set; } = NullLogger.Instance; - IVelopackLocator _locator; - VelopackHook _install; - VelopackHook _update; - VelopackHook _obsolete; - VelopackHook _uninstall; - VelopackHook _firstrun; - VelopackHook _restarted; - string[] _args; + internal static IVelopackLocator? DefaultLocator { get; private set; } + + VelopackHook? _install; + VelopackHook? _update; + VelopackHook? _obsolete; + VelopackHook? _uninstall; + VelopackHook? _firstrun; + VelopackHook? _restarted; + string[]? _args; bool _autoApply = true; private VelopackApp() @@ -64,10 +65,11 @@ namespace Velopack /// /// Override the default used to search for application paths. + /// This will be cached and potentially re-used throughout the lifetime of the application. /// public VelopackApp SetLocator(IVelopackLocator locator) { - _locator = locator; + DefaultLocator = locator; return this; } @@ -146,7 +148,7 @@ namespace Velopack /// /// A logging interface for diagnostic messages. This will be /// cached and potentially re-used throughout the lifetime of the application. - public void Run(ILogger logger = null) + public void Run(ILogger? logger = null) { var args = _args ?? Environment.GetCommandLineArgs().Skip(1).ToArray(); @@ -158,7 +160,7 @@ namespace Velopack } var log = logger ?? NullLogger.Instance; - var locator = _locator ?? VelopackLocator.GetDefault(log); + var locator = DefaultLocator ?? VelopackLocator.GetDefault(log); DefaultLogger = log; log.Info("Starting Velopack App (Run)."); @@ -193,6 +195,10 @@ namespace Velopack // some initial setup/state var myVersion = locator.CurrentlyInstalledVersion; + if (myVersion == null) { + return; + } + var firstrun = !String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("VELOPACK_FIRSTRUN")); var restarted = !String.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("VELOPACK_RESTART")); var localPackages = locator.GetLocalPackages(); @@ -204,23 +210,26 @@ namespace Velopack log.Info($"Launching app is out-dated. Current: {myVersion}, Newest Local Available: {latestLocal.Version}"); if (!restarted && _autoApply) { log.Info("Auto apply is true, so restarting to apply update..."); - var um = new UpdateManager(log, locator); - um.ApplyUpdatesAndRestart(args); + UpdateExe.Apply(locator, true, true, args, log); + } else { + log.Info("Pre-condition failed, we will not restart to apply updates. (restarted: " + restarted + ", autoApply: " + _autoApply + ")"); } } // clean up old versions of the app var pkgPath = locator.PackagesDir; - foreach (var package in localPackages) { - if (package.Type == VelopackAssetType.Full && (package.Version == latestLocal.Version || package.Version == myVersion)) { - continue; - } - try { - log.Info("Removing old package: " + package.FileName); - var p = Path.Combine(pkgPath, package.FileName); - File.Delete(p); - } catch (Exception ex) { - log.Error(ex, $"Failed to remove old package '{package.FileName}'"); + if (pkgPath != null) { + foreach (var package in localPackages) { + if (package.Type == VelopackAssetType.Full && (package.Version == latestLocal?.Version || package.Version == myVersion)) { + continue; + } + try { + log.Info("Removing old package: " + package.FileName); + var p = Path.Combine(pkgPath, package.FileName); + File.Delete(p); + } catch (Exception ex) { + log.Error(ex, $"Failed to remove old package '{package.FileName}'"); + } } } diff --git a/src/Velopack/VelopackAsset.cs b/src/Velopack/VelopackAsset.cs index f434f5c1..95eeb03a 100644 --- a/src/Velopack/VelopackAsset.cs +++ b/src/Velopack/VelopackAsset.cs @@ -1,4 +1,5 @@ -using System; +#nullable disable +using System; using System.IO; using NuGet.Versioning; using Velopack.Json; @@ -32,7 +33,7 @@ namespace Velopack /// public static VelopackAssetFeed FromJson(string json) { - return SimpleJson.DeserializeObject(json); + return SimpleJson.DeserializeObject(json) ?? new VelopackAssetFeed(); } } @@ -57,7 +58,7 @@ namespace Velopack public string SHA1 { get; init; } /// The size in bytes of the update package containing this release. - public long? Size { get; init; } + public long Size { get; init; } /// The release notes in markdown format, as passed to Velopack when packaging the release. public string NotesMarkdown { get; init; } diff --git a/src/Velopack/VelopackRuntimeInfo.cs b/src/Velopack/VelopackRuntimeInfo.cs index e16e8557..9777e274 100644 --- a/src/Velopack/VelopackRuntimeInfo.cs +++ b/src/Velopack/VelopackRuntimeInfo.cs @@ -1,8 +1,7 @@ -using System; +#nullable disable +using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Reflection; using System.Runtime.InteropServices; using System.Runtime.Versioning; using NuGet.Versioning; diff --git a/src/Velopack/Windows/RuntimeInfo.cs b/src/Velopack/Windows/RuntimeInfo.cs index 78034234..07d39674 100644 --- a/src/Velopack/Windows/RuntimeInfo.cs +++ b/src/Velopack/Windows/RuntimeInfo.cs @@ -1,4 +1,5 @@ -using System; +#nullable disable +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; diff --git a/src/Velopack/Windows/Runtimes.cs b/src/Velopack/Windows/Runtimes.cs index c1c7d3d0..bbf61785 100644 --- a/src/Velopack/Windows/Runtimes.cs +++ b/src/Velopack/Windows/Runtimes.cs @@ -1,4 +1,5 @@ -using System; +#nullable disable +using System; using System.Linq; namespace Velopack.Windows diff --git a/src/Velopack/Windows/ShellLink.cs b/src/Velopack/Windows/ShellLink.cs index 288be2e9..c19a04d8 100644 --- a/src/Velopack/Windows/ShellLink.cs +++ b/src/Velopack/Windows/ShellLink.cs @@ -1,6 +1,8 @@ -#pragma warning disable CS1591 +#nullable disable +#pragma warning disable CS1591 using System; +using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; @@ -13,6 +15,7 @@ namespace Velopack.Windows /// Summary description for ShellLink. /// [SupportedOSPlatform("windows")] + [ExcludeFromCodeCoverage] public class ShellLink : IDisposable { [ComImport()] @@ -932,6 +935,7 @@ namespace Velopack.Windows /// Enables extraction of icons for any file type from /// the Shell. /// + [ExcludeFromCodeCoverage] [SupportedOSPlatform("windows")] public class FileIcon { diff --git a/src/Velopack/Windows/Shortcuts.cs b/src/Velopack/Windows/Shortcuts.cs index 33861248..d03bcba2 100644 --- a/src/Velopack/Windows/Shortcuts.cs +++ b/src/Velopack/Windows/Shortcuts.cs @@ -1,4 +1,5 @@ -using System; +#nullable disable +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -222,9 +223,9 @@ namespace Velopack.Windows } /// - /// Given an and return the target shortcut path. + /// Given an and return the target shortcut path. /// - protected virtual string LinkPathForVersionInfo(ShortcutLocation location, IPackage package, FileVersionInfo versionInfo, string rootdir) + protected virtual string LinkPathForVersionInfo(ShortcutLocation location, ZipPackage package, FileVersionInfo versionInfo, string rootdir) { var possibleProductNames = new[] { versionInfo.ProductName, diff --git a/test/TestApp/Program.cs b/test/TestApp/Program.cs index 46648b84..15c358f6 100644 --- a/test/TestApp/Program.cs +++ b/test/TestApp/Program.cs @@ -76,7 +76,7 @@ try { return -1; } Console.WriteLine("applying..."); - um.ApplyUpdatesAndRestart(new[] { "test", "args !!" }); + um.ApplyPendingUpdate(true, new[] { "test", "args !!" }); return 0; } } diff --git a/test/Velopack.Tests/TestHelpers/FakeFixtureRepository.cs b/test/Velopack.Tests/TestHelpers/FakeFixtureRepository.cs index c61aea8b..d06d66e1 100644 --- a/test/Velopack.Tests/TestHelpers/FakeFixtureRepository.cs +++ b/test/Velopack.Tests/TestHelpers/FakeFixtureRepository.cs @@ -43,6 +43,7 @@ namespace Velopack.Tests.TestHelpers Version = maxDeltaVer.Version, Type = VelopackAssetType.Full, FileName = $"{maxfullVer.PackageId}-{maxDeltaVer.Version}-full.nupkg", + Size = maxfullVer.Filesize, }); } } diff --git a/test/Velopack.Tests/UpdateManagerTests.cs b/test/Velopack.Tests/UpdateManagerTests.cs index 7be49cd2..24ef088c 100644 --- a/test/Velopack.Tests/UpdateManagerTests.cs +++ b/test/Velopack.Tests/UpdateManagerTests.cs @@ -235,7 +235,7 @@ namespace Velopack.Tests var repo = new FakeFixtureRepository(id, true); var source = new SimpleWebSource("http://any.com", repo); - var feed = await source.GetReleaseFeed(logger); + var feed = await source.GetReleaseFeed(logger, VelopackRuntimeInfo.SystemOs.GetOsShortName()); var basePkg = feed.Assets .Where(x => x.Type == VelopackAssetType.Full) .Single(x => x.Version == SemanticVersion.Parse(fromVersion)); diff --git a/test/Velopack.Tests/ZipPackageTests.cs b/test/Velopack.Tests/ZipPackageTests.cs index 632d2d5a..367b13cf 100644 --- a/test/Velopack.Tests/ZipPackageTests.cs +++ b/test/Velopack.Tests/ZipPackageTests.cs @@ -17,12 +17,10 @@ namespace Velopack.Tests File.Copy(inputPackage, copyPackage); var zp = new ZipPackage(inputPackage); - var zipfw = zp.Frameworks; var zipf = zp.Files.OrderBy(f => f.Path).ToArray(); var zipfLib = zp.Files.Where(f => f.IsLibFile()).OrderBy(f => f.Path).ToArray(); using Package package = Package.Open(copyPackage); - var packagingfw = GetSupportedFrameworks(zp, package); var packaging = GetFiles(package).OrderBy(f => f.Path).ToArray(); var packagingLib = GetLibFiles(package).OrderBy(f => f.Path).ToArray(); @@ -31,7 +29,6 @@ namespace Velopack.Tests // throw new Exception(); //} - Assert.Equal(packagingfw, zipfw); Assert.Equal(packaging, zipf); Assert.Equal(packagingLib, zipfLib); } @@ -55,53 +52,24 @@ namespace Velopack.Tests Assert.Equal("Copyright ©", dyn.Copyright); Assert.Equal("en-US", zp.Language); Assert.Equal("Squirrel for Windows", dyn.Title); - - Assert.NotEmpty(zp.DependencySets); - var net461 = zp.DependencySets.First(); - Assert.Equal(new[] { ".NETFramework4.6.1" }, net461.SupportedFrameworks); - Assert.Equal(".NETFramework4.6.1", net461.TargetFramework); - - Assert.NotEmpty(net461.Dependencies); - var dvt = net461.Dependencies.First(); - Assert.Equal("System.ValueTuple", dvt.Id); - Assert.Equal("4.5.0", dvt.VersionSpec); - - Assert.Equal(new[] { "net5.0" }, zp.DependencySets.Last().SupportedFrameworks); - - Assert.NotEmpty(zp.FrameworkAssemblies); - var fw = zp.FrameworkAssemblies.First(); - Assert.Equal("System.Net.Http", fw.AssemblyName); - Assert.Equal(new[] { ".NETFramework4.6.1" }, fw.SupportedFrameworks); } - IEnumerable GetSupportedFrameworks(ZipPackage zp, Package package) - { - var fileFrameworks = from part in package.GetParts() - where IsPackageFile(part) - select NugetUtil.ParseFrameworkNameFromFilePath(NugetUtil.GetPath(part.Uri), out var effectivePath); - - return zp.FrameworkAssemblies.SelectMany(f => f.SupportedFrameworks) - .Concat(fileFrameworks) - .Where(f => f != null) - .Distinct(); - } - - IEnumerable GetLibFiles(Package package) + IEnumerable GetLibFiles(Package package) { return GetFiles(package, NugetUtil.LibDirectory); } - IEnumerable GetFiles(Package package, string directory) + IEnumerable GetFiles(Package package, string directory) { string folderPrefix = directory + Path.DirectorySeparatorChar; return GetFiles(package).Where(file => file.Path.StartsWith(folderPrefix, StringComparison.OrdinalIgnoreCase)); } - List GetFiles(Package package) + List GetFiles(Package package) { return (from part in package.GetParts() where IsPackageFile(part) - select (IPackageFile) new ZipPackageFile(part.Uri)).ToList(); + select new ZipPackageFile(part.Uri)).ToList(); } bool IsPackageFile(PackagePart part)