mirror of
https://github.com/velopack/velopack.git
synced 2025-10-25 15:19:22 +00:00
Add more specific exception types for lockfile and notinstalled
This commit is contained in:
@@ -5,6 +5,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Velopack.Exceptions;
|
||||
using Velopack.Util;
|
||||
|
||||
namespace Velopack.Compression
|
||||
@@ -62,31 +63,34 @@ namespace Velopack.Compression
|
||||
new DirectoryInfo(workingPath).GetAllFilesRecursively()
|
||||
.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");
|
||||
File.Delete(Path.Combine(workingPath, x));
|
||||
});
|
||||
.ForEach(
|
||||
x => {
|
||||
Log.Trace($"{x} was in old package but not in new one, deleting");
|
||||
File.Delete(Path.Combine(workingPath, x));
|
||||
});
|
||||
|
||||
progress(85);
|
||||
|
||||
// Add all of the files that are in the new package but
|
||||
// not in the old one.
|
||||
deltaPathRelativePaths
|
||||
.Where(x => x.StartsWith("lib", StringComparison.InvariantCultureIgnoreCase)
|
||||
&& !x.EndsWith(".shasum", StringComparison.InvariantCultureIgnoreCase)
|
||||
&& !pathsVisited.Contains(DIFF_SUFFIX.Replace(x, ""), StringComparer.InvariantCultureIgnoreCase))
|
||||
.ForEach(x => {
|
||||
Log.Trace($"{x} was in new package but not in old one, adding");
|
||||
.Where(
|
||||
x => x.StartsWith("lib", StringComparison.InvariantCultureIgnoreCase)
|
||||
&& !x.EndsWith(".shasum", StringComparison.InvariantCultureIgnoreCase)
|
||||
&& !pathsVisited.Contains(DIFF_SUFFIX.Replace(x, ""), StringComparer.InvariantCultureIgnoreCase))
|
||||
.ForEach(
|
||||
x => {
|
||||
Log.Trace($"{x} was in new package but not in old one, adding");
|
||||
|
||||
string outputFile = Path.Combine(workingPath, x);
|
||||
string outputDirectory = Path.GetDirectoryName(outputFile)!;
|
||||
string outputFile = Path.Combine(workingPath, x);
|
||||
string outputDirectory = Path.GetDirectoryName(outputFile)!;
|
||||
|
||||
if (!Directory.Exists(outputDirectory)) {
|
||||
Directory.CreateDirectory(outputDirectory);
|
||||
}
|
||||
if (!Directory.Exists(outputDirectory)) {
|
||||
Directory.CreateDirectory(outputDirectory);
|
||||
}
|
||||
|
||||
File.Copy(Path.Combine(deltaPath, x), outputFile);
|
||||
});
|
||||
File.Copy(Path.Combine(deltaPath, x), outputFile);
|
||||
});
|
||||
|
||||
progress(95);
|
||||
|
||||
@@ -94,20 +98,23 @@ namespace Velopack.Compression
|
||||
// package's versions (i.e. the nuspec file, etc etc).
|
||||
deltaPathRelativePaths
|
||||
.Where(x => !x.StartsWith("lib", StringComparison.InvariantCultureIgnoreCase))
|
||||
.ForEach(x => {
|
||||
Log.Trace($"Writing metadata file: {x}");
|
||||
File.Copy(Path.Combine(deltaPath, x), Path.Combine(workingPath, x), true);
|
||||
});
|
||||
.ForEach(
|
||||
x => {
|
||||
Log.Trace($"Writing metadata file: {x}");
|
||||
File.Copy(Path.Combine(deltaPath, x), Path.Combine(workingPath, x), true);
|
||||
});
|
||||
|
||||
// delete all metadata files that are not in the new package
|
||||
new DirectoryInfo(workingPath).GetAllFilesRecursively()
|
||||
.Select(x => x.FullName.Replace(workingPath + Path.DirectorySeparatorChar, "").ToLowerInvariant())
|
||||
.Where(x => !x.StartsWith("lib", StringComparison.InvariantCultureIgnoreCase)
|
||||
&& !deltaPathRelativePaths.Contains(x, StringComparer.InvariantCultureIgnoreCase))
|
||||
.ForEach(x => {
|
||||
Log.Trace($"Deleting removed metadata file: {x}");
|
||||
File.Delete(Path.Combine(workingPath, x));
|
||||
});
|
||||
.Where(
|
||||
x => !x.StartsWith("lib", StringComparison.InvariantCultureIgnoreCase)
|
||||
&& !deltaPathRelativePaths.Contains(x, StringComparer.InvariantCultureIgnoreCase))
|
||||
.ForEach(
|
||||
x => {
|
||||
Log.Trace($"Deleting removed metadata file: {x}");
|
||||
File.Delete(Path.Combine(workingPath, x));
|
||||
});
|
||||
|
||||
progress(100);
|
||||
}
|
||||
|
||||
20
src/lib-csharp/Exceptions/AcquireLockFailedException.cs
Normal file
20
src/lib-csharp/Exceptions/AcquireLockFailedException.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Velopack.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Thrown when an exclusive lock for an application cannot be acquired. Usually this means another
|
||||
/// instance of the application is running Velopack operations and the current instance cannot proceed.
|
||||
/// </summary>
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class AcquireLockFailedException : Exception
|
||||
{
|
||||
private const string DEFAULT_MESSAGE = "Failed to acquire exclusive lock file. Is another operation currently running?";
|
||||
|
||||
internal AcquireLockFailedException() : base(DEFAULT_MESSAGE) { }
|
||||
internal AcquireLockFailedException(Exception innerException) : base(DEFAULT_MESSAGE, innerException) { }
|
||||
internal AcquireLockFailedException(string message) : base(message) { }
|
||||
internal AcquireLockFailedException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Velopack.Compression
|
||||
namespace Velopack.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an error that occurs when a package does not match it's expected SHA checksum
|
||||
@@ -27,4 +27,4 @@ namespace Velopack.Compression
|
||||
FilePath = filePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
20
src/lib-csharp/Exceptions/NotInstalledException.cs
Normal file
20
src/lib-csharp/Exceptions/NotInstalledException.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Velopack.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Thrown when an operation can not be performed in an application that is not installed.
|
||||
/// </summary>
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class NotInstalledException : Exception
|
||||
{
|
||||
private const string DEFAULT_MESSAGE =
|
||||
"This operation can not be performed in an application that is not installed. Please install the application and try again.";
|
||||
|
||||
internal NotInstalledException() : base(DEFAULT_MESSAGE) { }
|
||||
internal NotInstalledException(Exception innerException) : base(DEFAULT_MESSAGE, innerException) { }
|
||||
internal NotInstalledException(string message) : base(message) { }
|
||||
internal NotInstalledException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
}
|
||||
@@ -42,10 +42,10 @@ namespace Velopack.NuGet
|
||||
{
|
||||
if (SemanticVersion.TryParse(version, out var parsed)) {
|
||||
if (parsed < new SemanticVersion(0, 0, 1, parsed.Release)) {
|
||||
throw new Exception($"Invalid package version '{version}', it must be >= 0.0.1.");
|
||||
throw new ArgumentException($"Invalid package version '{version}', it must be >= 0.0.1.");
|
||||
}
|
||||
} else {
|
||||
throw new Exception($"Invalid package version '{version}', it must be a 3-part SemVer2 compliant version string.");
|
||||
throw new ArgumentException($"Invalid package version '{version}', it must be a 3-part SemVer2 compliant version string.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using NuGet.Versioning;
|
||||
using Velopack.Compression;
|
||||
using Velopack.Exceptions;
|
||||
using Velopack.Locators;
|
||||
using Velopack.NuGet;
|
||||
using Velopack.Sources;
|
||||
@@ -97,6 +98,7 @@ namespace Velopack
|
||||
if (source == null) {
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
}
|
||||
|
||||
Source = source;
|
||||
Log = logger ?? VelopackApp.DefaultLogger ?? NullLogger.Instance;
|
||||
Locator = locator ?? VelopackApp.DefaultLocator ?? VelopackLocator.GetDefault(Log);
|
||||
@@ -149,12 +151,14 @@ namespace Velopack
|
||||
// and we're searching for a different channel than current
|
||||
if (ShouldAllowVersionDowngrade && IsNonDefaultChannel) {
|
||||
if (VersionComparer.Compare(latestRemoteFull.Version, installedVer, VersionComparison.Version) == 0) {
|
||||
Log.Info($"Latest remote release is the same version of a different channel, and downgrade is enabled ({installedVer}: {DefaultChannel} -> {Channel}).");
|
||||
Log.Info(
|
||||
$"Latest remote release is the same version of a different channel, and downgrade is enabled ({installedVer}: {DefaultChannel} -> {Channel}).");
|
||||
return new UpdateInfo(latestRemoteFull, true);
|
||||
}
|
||||
}
|
||||
|
||||
Log.Info($"No updates, remote version ({latestRemoteFull.Version}) is not newer than current version ({installedVer}) and / or downgrade is not enabled.");
|
||||
Log.Info(
|
||||
$"No updates, remote version ({latestRemoteFull.Version}) is not newer than current version ({installedVer}) and / or downgrade is not enabled.");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -214,6 +218,7 @@ namespace Velopack
|
||||
// the progress delegate may very likely invoke into the client main thread for UI updates, so
|
||||
// let's try to reduce the spam. report only on even numbers and only if the progress has changed.
|
||||
int lastProgress = 0;
|
||||
|
||||
void reportProgress(int x)
|
||||
{
|
||||
int result = (int) (Math.Round(x / 2d, MidpointRounding.AwayFromZero) * 2d);
|
||||
@@ -262,7 +267,8 @@ namespace Velopack
|
||||
Log.Info("Ignoring delta updates (ignoreDeltas parameter)");
|
||||
} else {
|
||||
if (deltasCount > 10 || deltasSize > targetRelease.Size) {
|
||||
Log.Info($"There are too many delta's ({deltasCount} > 10) or the sum of their size ({deltasSize} > {targetRelease.Size}) is too large. " +
|
||||
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 = TempUtil.GetTempDirectory(out var deltaStagingDir, appTempDir);
|
||||
@@ -272,13 +278,21 @@ namespace Velopack
|
||||
EasyZip.ExtractZipToDirectory(Log, basePackagePath, deltaStagingDir);
|
||||
|
||||
reportProgress(10);
|
||||
await DownloadAndApplyDeltaUpdates(deltaStagingDir, updates, x => reportProgress(CoreUtil.CalculateProgress(x, 10, 80)), cancelToken)
|
||||
await DownloadAndApplyDeltaUpdates(
|
||||
deltaStagingDir,
|
||||
updates,
|
||||
x => reportProgress(CoreUtil.CalculateProgress(x, 10, 80)),
|
||||
cancelToken)
|
||||
.ConfigureAwait(false);
|
||||
reportProgress(80);
|
||||
|
||||
Log.Info("Delta updates completed, creating final update package.");
|
||||
File.Delete(incompleteFile);
|
||||
await EasyZip.CreateZipFromDirectoryAsync(Log, incompleteFile, deltaStagingDir, x => reportProgress(CoreUtil.CalculateProgress(x, 80, 100)),
|
||||
await EasyZip.CreateZipFromDirectoryAsync(
|
||||
Log,
|
||||
incompleteFile,
|
||||
deltaStagingDir,
|
||||
x => reportProgress(CoreUtil.CalculateProgress(x, 80, 100)),
|
||||
cancelToken: cancelToken).ConfigureAwait(false);
|
||||
File.Delete(completeFile);
|
||||
File.Move(incompleteFile, completeFile);
|
||||
@@ -311,11 +325,12 @@ namespace Velopack
|
||||
if (zip.UpdateExeBytes == null) {
|
||||
Log.Error("Update.exe not found in package, skipping extraction.");
|
||||
} else {
|
||||
await IoUtil.RetryAsync(async () => {
|
||||
using var ms = new MemoryStream(zip.UpdateExeBytes);
|
||||
using var fs = File.Create(updateExe);
|
||||
await ms.CopyToAsync(fs).ConfigureAwait(false);
|
||||
}).ConfigureAwait(false);
|
||||
await IoUtil.RetryAsync(
|
||||
async () => {
|
||||
using var ms = new MemoryStream(zip.UpdateExeBytes);
|
||||
using var fs = File.Create(updateExe);
|
||||
await ms.CopyToAsync(fs).ConfigureAwait(false);
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Log.Error(ex, "Failed to extract new Update.exe");
|
||||
@@ -334,7 +349,8 @@ namespace Velopack
|
||||
/// <param name="updates">An update object containing one or more delta's</param>
|
||||
/// <param name="progress">A callback reporting process of delta application progress (from 0-100).</param>
|
||||
/// <param name="cancelToken">A token to use to cancel the request.</param>
|
||||
protected virtual async Task DownloadAndApplyDeltaUpdates(string extractedBasePackage, UpdateInfo updates, Action<int> progress, CancellationToken cancelToken)
|
||||
protected virtual async Task DownloadAndApplyDeltaUpdates(string extractedBasePackage, UpdateInfo updates, Action<int> progress,
|
||||
CancellationToken cancelToken)
|
||||
{
|
||||
var releasesToDownload = updates.DeltasToTarget.OrderBy(d => d.Version).ToArray();
|
||||
|
||||
@@ -344,22 +360,28 @@ namespace Velopack
|
||||
// downloading accounts for 0%-50% of progress
|
||||
double current = 0;
|
||||
double toIncrement = 100.0 / releasesToDownload.Count();
|
||||
await releasesToDownload.ForEachAsync(async x => {
|
||||
var targetFile = Locator.GetLocalPackagePath(x);
|
||||
double component = 0;
|
||||
Log.Debug($"Downloading delta version {x.Version}");
|
||||
await Source.DownloadReleaseEntry(Log, x, targetFile, p => {
|
||||
lock (progress) {
|
||||
current -= component;
|
||||
component = toIncrement / 100.0 * p;
|
||||
var progressOfStep = (int) Math.Round(current += component);
|
||||
progress(CoreUtil.CalculateProgress(progressOfStep, 0, 50));
|
||||
}
|
||||
}, cancelToken).ConfigureAwait(false);
|
||||
VerifyPackageChecksum(x, targetFile);
|
||||
cancelToken.ThrowIfCancellationRequested();
|
||||
Log.Debug($"Download complete for delta version {x.Version}");
|
||||
}).ConfigureAwait(false);
|
||||
await releasesToDownload.ForEachAsync(
|
||||
async x => {
|
||||
var targetFile = Locator.GetLocalPackagePath(x);
|
||||
double component = 0;
|
||||
Log.Debug($"Downloading delta version {x.Version}");
|
||||
await Source.DownloadReleaseEntry(
|
||||
Log,
|
||||
x,
|
||||
targetFile,
|
||||
p => {
|
||||
lock (progress) {
|
||||
current -= component;
|
||||
component = toIncrement / 100.0 * p;
|
||||
var progressOfStep = (int) Math.Round(current += component);
|
||||
progress(CoreUtil.CalculateProgress(progressOfStep, 0, 50));
|
||||
}
|
||||
},
|
||||
cancelToken).ConfigureAwait(false);
|
||||
VerifyPackageChecksum(x, targetFile);
|
||||
cancelToken.ThrowIfCancellationRequested();
|
||||
Log.Debug($"Download complete for delta version {x.Version}");
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
Log.Info("All delta packages downloaded and verified, applying them to the base now. The delta staging dir is: " + extractedBasePackage);
|
||||
|
||||
@@ -371,10 +393,13 @@ namespace Velopack
|
||||
var rel = releasesToDownload[i];
|
||||
double baseProgress = i * progressStepSize;
|
||||
var packageFile = Locator.GetLocalPackagePath(rel);
|
||||
builder.ApplyDeltaPackageFast(extractedBasePackage, packageFile, x => {
|
||||
var progressOfStep = (int) (baseProgress + (progressStepSize * (x / 100d)));
|
||||
progress(CoreUtil.CalculateProgress(progressOfStep, 50, 100));
|
||||
});
|
||||
builder.ApplyDeltaPackageFast(
|
||||
extractedBasePackage,
|
||||
packageFile,
|
||||
x => {
|
||||
var progressOfStep = (int) (baseProgress + (progressStepSize * (x / 100d)));
|
||||
progress(CoreUtil.CalculateProgress(progressOfStep, 50, 100));
|
||||
});
|
||||
}
|
||||
|
||||
progress(100);
|
||||
@@ -443,7 +468,7 @@ namespace Velopack
|
||||
if (!hash.Equals(release.SHA1, StringComparison.OrdinalIgnoreCase)) {
|
||||
throw new ChecksumFailedException(targetPackage.FullName, $"SHA1 doesn't match ({release.SHA1} != {hash}).");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -452,7 +477,7 @@ namespace Velopack
|
||||
protected virtual void EnsureInstalled()
|
||||
{
|
||||
if (AppId == null || !IsInstalled)
|
||||
throw new Exception("Cannot perform this operation in an application which is not installed.");
|
||||
throw new NotInstalledException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -472,6 +497,7 @@ namespace Velopack
|
||||
if (String.IsNullOrWhiteSpace(urlOrPath)) {
|
||||
throw new ArgumentException("Must pass a valid URL or file path to UpdateManager", nameof(urlOrPath));
|
||||
}
|
||||
|
||||
if (HttpUtil.IsHttpUrl(urlOrPath)) {
|
||||
return new SimpleWebSource(urlOrPath, HttpUtil.CreateDefaultDownloader());
|
||||
} else {
|
||||
@@ -479,4 +505,4 @@ namespace Velopack
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using System.Runtime.Versioning;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Velopack.Exceptions;
|
||||
|
||||
namespace Velopack.Util
|
||||
{
|
||||
@@ -45,7 +46,7 @@ namespace Velopack.Util
|
||||
IsLocked = true;
|
||||
} catch (Exception ex) {
|
||||
DisposeInternal();
|
||||
throw new IOException("Failed to acquire exclusive lock file. Is another operation currently running?", ex);
|
||||
throw new AcquireLockFailedException(ex);
|
||||
} finally {
|
||||
_semaphore.Release();
|
||||
}
|
||||
@@ -78,6 +79,7 @@ namespace Velopack.Util
|
||||
if (_fileDescriptor > 0) {
|
||||
close(_fileDescriptor);
|
||||
}
|
||||
|
||||
var fileBytes = Encoding.UTF8.GetBytes(_filePath).ToArray();
|
||||
|
||||
const int O_RDWR = 0x2;
|
||||
@@ -108,7 +110,7 @@ namespace Velopack.Util
|
||||
close(fd);
|
||||
throw new IOException($"lockf failed, errno: {errno}", new Win32Exception(errno));
|
||||
}
|
||||
|
||||
|
||||
_fileDescriptor = fd;
|
||||
}
|
||||
|
||||
@@ -129,7 +131,7 @@ namespace Velopack.Util
|
||||
private void DisposeInternal()
|
||||
{
|
||||
Interlocked.Exchange(ref this._fileStream, null)?.Dispose();
|
||||
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
|
||||
if (_fileDescriptor > 0) {
|
||||
close(_fileDescriptor);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using NuGet.Versioning;
|
||||
using Velopack.Compression;
|
||||
using Velopack.Core;
|
||||
using Velopack.Exceptions;
|
||||
using Velopack.Locators;
|
||||
using Velopack.Sources;
|
||||
using Velopack.Tests.TestHelpers;
|
||||
|
||||
Reference in New Issue
Block a user