Skip to content
This repository has been archived by the owner on Jul 5, 2024. It is now read-only.

Commit

Permalink
Add download/delta tests
Browse files Browse the repository at this point in the history
  • Loading branch information
caesay committed Dec 21, 2023
1 parent 161d238 commit 42a9ea5
Show file tree
Hide file tree
Showing 6 changed files with 250 additions and 74 deletions.
14 changes: 11 additions & 3 deletions src/Squirrel/Locators/TestSquirrelLocator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class TestSquirrelLocator : SquirrelLocator
public override string AppId { get; }

/// <inheritdoc />
public override string RootAppDir => AppContext.BaseDirectory;
public override string RootAppDir { get; }

/// <inheritdoc />
public override string PackagesDir { get; }
Expand All @@ -34,15 +34,23 @@ public class TestSquirrelLocator : SquirrelLocator
public override SemanticVersion CurrentlyInstalledVersion { get; }

/// <inheritdoc />
public override string AppContentDir => AppContext.BaseDirectory;
public override string AppContentDir { get; }

/// <inheritdoc cref="TestSquirrelLocator" />
public TestSquirrelLocator(string appId, string version, string packagesDir, ILogger logger)
public TestSquirrelLocator(string appId, string version, string packagesDir, ILogger logger = null)
: this(appId, version, packagesDir, AppContext.BaseDirectory, AppContext.BaseDirectory, logger)
{
}

/// <inheritdoc cref="TestSquirrelLocator" />
public TestSquirrelLocator(string appId, string version, string packagesDir, string appDir, string rootDir, ILogger logger = null)
: base(logger)
{
AppId = appId;
PackagesDir = packagesDir;
CurrentlyInstalledVersion = SemanticVersion.Parse(version);
RootAppDir = rootDir;
AppContentDir = appDir;
}
}
}
21 changes: 11 additions & 10 deletions src/Squirrel/ReleaseEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -300,9 +300,8 @@ public static ReleaseEntry GenerateFromFile(string path, string baseUrl = null)
/// package files. Also writes/updates a RELEASES file in the specified directory
/// to match the packages the are currently present.
/// </summary>
/// <param name="releasePackagesDir">The local directory to read and update</param>
/// <returns>The list of packages in the directory</returns>
public static List<ReleaseEntry> BuildReleasesFile(string releasePackagesDir)
public static List<ReleaseEntry> BuildReleasesFile(string releasePackagesDir, bool writeToDisk = true)
{
var packagesDir = new DirectoryInfo(releasePackagesDir);

Expand All @@ -317,18 +316,20 @@ public static List<ReleaseEntry> BuildReleasesFile(string releasePackagesDir)
// Write the new RELEASES file to a temp file then move it into
// place
var entries = entriesQueue.ToList();
using var _ = Utility.GetTempFileName(out var tempFile);

using (var of = File.OpenWrite(tempFile)) {
if (entries.Count > 0) WriteReleaseFile(entries, of);
}
if (writeToDisk) {
using var _ = Utility.GetTempFileName(out var tempFile);
using (var of = File.OpenWrite(tempFile)) {
if (entries.Count > 0) WriteReleaseFile(entries, of);
}
var target = Path.Combine(packagesDir.FullName, "RELEASES");
if (File.Exists(target)) {
File.Delete(target);
}

var target = Path.Combine(packagesDir.FullName, "RELEASES");
if (File.Exists(target)) {
File.Delete(target);
File.Move(tempFile, target);
}

File.Move(tempFile, target);
return entries;
}

Expand Down
153 changes: 92 additions & 61 deletions src/Squirrel/UpdateManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,77 +117,81 @@ public void DownloadAndPrepareUpdates(UpdateInfo updates, Action<int> progress =
public virtual async Task DownloadAndPrepareUpdatesAsync(
UpdateInfo updates, Action<int> progress = null, bool ignoreDeltas = false, CancellationToken cancelToken = default)
{
progress ??= (_ => { });
var targetRelease = updates?.TargetFullRelease;
if (targetRelease == null) {
throw new ArgumentException("Must pass a valid UpdateInfo object with a non-null TargetFullRelease", nameof(updates));
}
try {
progress ??= (_ => { });
var targetRelease = updates?.TargetFullRelease;
if (targetRelease == null) {
throw new ArgumentException("Must pass a valid UpdateInfo object with a non-null TargetFullRelease", nameof(updates));
}

EnsureInstalled();
using var _mut = AcquireUpdateLock();
EnsureInstalled();
using var _mut = AcquireUpdateLock();

var completeFile = Path.Combine(Locator.PackagesDir, targetRelease.OriginalFilename);
var incompleteFile = completeFile + ".downloading";
var completeFile = Path.Combine(Locator.PackagesDir, targetRelease.OriginalFilename);
var incompleteFile = completeFile + ".partial";

if (File.Exists(completeFile)) {
Log.Info($"Package already exists on disk: '{completeFile}', verifying checksum...");
try {
VerifyPackageChecksum(targetRelease);
} catch (ChecksumFailedException ex) {
Log.Warn(ex, $"Checksum failed for file '{completeFile}'. Deleting and starting over.");
if (File.Exists(completeFile)) {
Log.Info($"Package already exists on disk: '{completeFile}', verifying checksum...");
try {
VerifyPackageChecksum(targetRelease);
} catch (ChecksumFailedException ex) {
Log.Warn(ex, $"Checksum failed for file '{completeFile}'. Deleting and starting over.");
}
}
}

var deltasSize = updates.DeltasToTarget.Sum(x => x.Filesize);
var deltasCount = updates.DeltasToTarget.Count();
var deltasSize = updates.DeltasToTarget.Sum(x => x.Filesize);
var deltasCount = updates.DeltasToTarget.Count();

try {
if (deltasCount > 0) {
if (ignoreDeltas) {
Log.Info("Ignoring delta updates (ignoreDeltas parameter)");
} else {
if (deltasSize > 10 || deltasSize > targetRelease.Filesize) {
Log.Info($"There are too many delta's ({deltasCount} > 10) or the sum of their size ({deltasSize}) is too large. " +
$"Only full update will be available.");
try {
if (deltasCount > 0) {
if (ignoreDeltas) {
Log.Info("Ignoring delta updates (ignoreDeltas parameter)");
} else {
var _1 = Utility.GetTempDirectory(out var deltaStagingDir, Locator.AppTempDir);
if (updates.BaseRelease?.OriginalFilename != null) {
string basePackagePath = Path.Combine(Locator.PackagesDir, updates.BaseRelease.OriginalFilename);
if (!File.Exists(basePackagePath))
throw new Exception($"Unable to find base package {basePackagePath} for delta update.");
EasyZip.ExtractZipToDirectory(Log, basePackagePath, deltaStagingDir);
if (deltasCount > 10 || deltasSize > targetRelease.Filesize) {
Log.Info($"There are too many delta's ({deltasCount} > 10) or the sum of their size ({deltasSize} > {targetRelease.Filesize}) is too large. " +
$"Only full update will be available.");
} else {
Log.Warn("No base package available. Attempting delta update using application files.");
Utility.CopyFiles(Locator.AppContentDir, deltaStagingDir);
var _1 = Utility.GetTempDirectory(out var deltaStagingDir, Locator.AppTempDir);
if (updates.BaseRelease?.OriginalFilename != null) {
string basePackagePath = Path.Combine(Locator.PackagesDir, updates.BaseRelease.OriginalFilename);
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);
}
progress(10);
await DownloadAndApplyDeltaUpdates(deltaStagingDir, updates, x => Utility.CalculateProgress(x, 10, 90))
.ConfigureAwait(false);
progress(90);

Log.Info("Delta updates completed, creating final update package.");
File.Delete(incompleteFile);
EasyZip.CreateZipFromDirectory(Log, incompleteFile, deltaStagingDir);
File.Delete(completeFile);
File.Move(incompleteFile, completeFile);
Log.Info("Delta release preparations complete. Package moved to: " + completeFile);
progress(100);
return; // success!
}
progress(10);
await DownloadAndApplyDeltaUpdates(deltaStagingDir, updates, x => Utility.CalculateProgress(x, 10, 90))
.ConfigureAwait(false);
progress(90);

Log.Info("Delta updates completed, creating final update package.");
File.Delete(incompleteFile);
EasyZip.CreateZipFromDirectory(Log, incompleteFile, deltaStagingDir);
File.Delete(completeFile);
File.Move(incompleteFile, completeFile);
Log.Info("Delta release preparations complete. Package moved to: " + completeFile);
progress(100);
return; // success!
}
}
} catch (Exception ex) {
Log.Warn(ex, "Unable to apply delta updates, falling back to full update.");
}
} catch (Exception ex) {
Log.Warn(ex, "Unable to apply delta updates, falling back to full update.");
}

Log.Info($"Downloading full release ({targetRelease.OriginalFilename})");
File.Delete(incompleteFile);
await Source.DownloadReleaseEntry(targetRelease, incompleteFile, progress).ConfigureAwait(false);
Log.Info("Verifying package checksum...");
VerifyPackageChecksum(targetRelease, incompleteFile);
File.Delete(completeFile);
File.Move(incompleteFile, completeFile);
Log.Info("Full release download complete. Package moved to: " + completeFile);
Log.Info($"Downloading full release ({targetRelease.OriginalFilename})");
File.Delete(incompleteFile);
await Source.DownloadReleaseEntry(targetRelease, incompleteFile, progress).ConfigureAwait(false);
Log.Info("Verifying package checksum...");
VerifyPackageChecksum(targetRelease, incompleteFile);
File.Delete(completeFile);
File.Move(incompleteFile, completeFile);
Log.Info("Full release download complete. Package moved to: " + completeFile);
} finally {
CleanIncompleteAndDeltaPackages();
}
}

protected virtual async Task DownloadAndApplyDeltaUpdates(string extractedBasePackage, UpdateInfo updates, Action<int> progress)
Expand Down Expand Up @@ -232,7 +236,36 @@ await Source.DownloadReleaseEntry(x, targetFile, p => {
progress(100);
}

protected virtual void VerifyPackageChecksum(ReleaseEntry release, string filePathOverride = null)
protected void CleanIncompleteAndDeltaPackages()
{
try {
Log.Info("Cleaning up incomplete and delta packages from packages directory.");
foreach (var l in Locator.GetLocalPackages()) {
if (l.IsDelta) {
try {
var pkgPath = Path.Combine(Locator.PackagesDir, l.OriginalFilename);
File.Delete(pkgPath);
Log.Trace(pkgPath + " deleted.");
} catch (Exception ex) {
Log.Warn(ex, "Failed to delete delta package: " + l.OriginalFilename);
}
}
}

foreach (var l in Directory.EnumerateFiles(Locator.PackagesDir, "*.partial").ToArray()) {
try {
File.Delete(l);
Log.Trace(l + " deleted.");
} catch (Exception ex) {
Log.Warn(ex, "Failed to delete partial package: " + l);
}
}
} catch (Exception ex) {
Log.Warn(ex, "Failed to clean up incomplete and delta packages.");
}
}

protected internal virtual void VerifyPackageChecksum(ReleaseEntry release, string filePathOverride = null)
{
var targetPackage = filePathOverride == null
? new FileInfo(Path.Combine(Locator.PackagesDir, release.OriginalFilename))
Expand All @@ -243,13 +276,11 @@ protected virtual void VerifyPackageChecksum(ReleaseEntry release, string filePa
}

if (targetPackage.Length != release.Filesize) {
targetPackage.Delete();
throw new ChecksumFailedException(targetPackage.FullName, $"Size doesn't match ({targetPackage.Length} != {release.Filesize}).");
}

var hash = Utility.CalculateFileSHA1(targetPackage.FullName);
if (!hash.Equals(release.SHA1, StringComparison.OrdinalIgnoreCase)) {
targetPackage.Delete();
throw new ChecksumFailedException(targetPackage.FullName, $"SHA1 doesn't match ({release.SHA1} != {hash}).");
}
}
Expand Down
79 changes: 79 additions & 0 deletions test/Squirrel.Tests/TestHelpers/FakeFixtureRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Squirrel.Tests.TestHelpers
{
internal class FakeFixtureRepository : Sources.IFileDownloader
{
private readonly string _pkgId;
private readonly IEnumerable<ReleaseEntry> _releases;

public FakeFixtureRepository(string pkgId, bool mockLatestFullVer)
{
_pkgId = pkgId;
var releases = ReleaseEntry.BuildReleasesFile(IntegrationTestHelper.GetFixturesPath(), false)
.Where(r => r.OriginalFilename.StartsWith(_pkgId))
.ToList();

if (mockLatestFullVer) {
var minFullVer = releases.Where(r => !r.IsDelta).MinBy(r => r.Version);
var maxfullVer = releases.Where(r => !r.IsDelta).MaxBy(r => r.Version).Single();
var maxDeltaVer = releases.Where(r => r.IsDelta).MaxBy(r => r.Version).Single();

// our fixtures don't have a full package for the latest version, we expect the tests to generate this file
if (maxfullVer.Version < maxDeltaVer.Version) {
var name = new ReleaseEntryName(maxfullVer.PackageId, maxDeltaVer.Version, false, maxfullVer.Rid);
releases.Add(new ReleaseEntry("0000000000000000000000000000000000000000", name.ToFileName(), maxfullVer.Filesize));
}
}

_releases = releases;
}

public Task<byte[]> DownloadBytes(string url, string authorization = null, string accept = null)
{
if (url.Contains("/RELEASES?")) {
MemoryStream ms = new MemoryStream();
ReleaseEntry.WriteReleaseFile(_releases, ms);
return Task.FromResult(ms.ToArray());
}

var rel = _releases.FirstOrDefault(r => url.EndsWith(r.OriginalFilename));
var filePath = IntegrationTestHelper.GetPath("fixtures", rel.OriginalFilename);
if (!File.Exists(filePath)) {
throw new NotSupportedException("FakeFixtureRepository doesn't have: " + rel.OriginalFilename);
}

return Task.FromResult(File.ReadAllBytes(filePath));
}

public Task DownloadFile(string url, string targetFile, Action<int> progress, string authorization = null, string accept = null)
{
var rel = _releases.FirstOrDefault(r => url.EndsWith(r.OriginalFilename));
var filePath = IntegrationTestHelper.GetPath("fixtures", rel.OriginalFilename);
if (!File.Exists(filePath)) {
throw new NotSupportedException("FakeFixtureRepository doesn't have: " + rel.OriginalFilename);
}

File.Copy(filePath, targetFile);
progress(25);
progress(50);
progress(75);
progress(100);
return Task.CompletedTask;
}

public Task<string> DownloadString(string url, string authorization = null, string accept = null)
{
if (!url.Contains("/RELEASES?")) {
throw new NotImplementedException();
}
MemoryStream ms = new MemoryStream();
ReleaseEntry.WriteReleaseFile(_releases, ms);
return Task.FromResult(Encoding.UTF8.GetString(ms.ToArray()));
}
}
}
2 changes: 2 additions & 0 deletions test/Squirrel.Tests/TestHelpers/IntegrationTestHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public static string GetIntegrationTestRootDirectory()
return di.FullName;
}

public static string GetFixturesPath() => GetPath("fixtures");

public static bool SkipTestOnXPAndVista()
{
int osVersion = Environment.OSVersion.Version.Major * 100 + Environment.OSVersion.Version.Minor;
Expand Down
Loading

0 comments on commit 42a9ea5

Please sign in to comment.