From 86a2a296bce33aabb42b461704338662ac4ca441 Mon Sep 17 00:00:00 2001 From: Aaron Pearson Date: Sun, 14 Jan 2024 12:47:17 +0000 Subject: [PATCH] Added simple Client for Microsoft Store No clue at all if it works, more doing this so I can plan out what I'm doing for clients * Revert some BSDiff changes which caused it to break * ICollection -> IReadOnlyCollection in IUpdatePackage --- TinyUpdate.sln | 7 ++ .../MicrosoftStoreClient.cs | 66 +++++++++++++++++++ .../TinyUpdate.MicrosoftStore.csproj | 13 ++++ .../TinyUpdate.Delta.BSDiff/BSDiffDelta.cs | 42 ++++++++---- .../TinyUpdate.Delta.BSDiff/StreamUtility.cs | 22 ------- .../TinyUpdate.Delta.MSDelta/MSDelta.cs | 3 +- src/Packages/TinyUpdate.TUUP/FileEntryExt.cs | 1 - .../TinyUpdate.TUUP/TuupUpdatePackage.cs | 30 +++++---- .../TuupUpdatePackageCreator.cs | 13 ++-- .../Abstract/{ => Delta}/IDeltaApplier.cs | 2 +- .../Abstract/{ => Delta}/IDeltaCreation.cs | 2 +- .../Abstract/{ => Delta}/IDeltaManager.cs | 2 +- .../Delta/IDeltaUpdatePackageCreator.cs | 27 ++++++++ .../Abstract/IPackageClient.cs | 28 ++++++++ .../Abstract/IUpdatePackage.cs | 10 +-- .../Abstract/IUpdatePackageCreator.cs | 22 +------ src/TinyUpdate.Core/Abstract/ReleaseEntry.cs | 12 ++++ src/TinyUpdate.Core/DeltaManager.cs | 9 ++- .../Model/DeltaCreationResult.cs | 2 +- src/TinyUpdate.Core/SHA256.cs | 21 +++--- .../Attributes/DeltaApplierAttribute.cs | 2 +- .../Attributes/DeltaCreationAttribute.cs | 2 +- .../TestSources/SHA256TestSource.cs | 4 +- .../Abstract/DeltaCan.cs | 1 + .../Abstract/DeltaManagerCan.cs | 3 +- .../DeltaManagerTests.cs | 2 +- .../Abstract/UpdatePackageCan.cs | 23 +++---- .../UpdatePackageTests.cs | 6 +- 28 files changed, 265 insertions(+), 112 deletions(-) create mode 100644 src/Clients/TinyUpdate.MicrosoftStore/MicrosoftStoreClient.cs create mode 100644 src/Clients/TinyUpdate.MicrosoftStore/TinyUpdate.MicrosoftStore.csproj delete mode 100644 src/Deltas/TinyUpdate.Delta.BSDiff/StreamUtility.cs rename src/TinyUpdate.Core/Abstract/{ => Delta}/IDeltaApplier.cs (95%) rename src/TinyUpdate.Core/Abstract/{ => Delta}/IDeltaCreation.cs (93%) rename src/TinyUpdate.Core/Abstract/{ => Delta}/IDeltaManager.cs (95%) create mode 100644 src/TinyUpdate.Core/Abstract/Delta/IDeltaUpdatePackageCreator.cs create mode 100644 src/TinyUpdate.Core/Abstract/IPackageClient.cs create mode 100644 src/TinyUpdate.Core/Abstract/ReleaseEntry.cs diff --git a/TinyUpdate.sln b/TinyUpdate.sln index edc6dbd..868246c 100644 --- a/TinyUpdate.sln +++ b/TinyUpdate.sln @@ -30,6 +30,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TinyUpdate.Packages.Tests", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TinyUpdate.Core.Tests", "tests\TinyUpdate.Core.Tests\TinyUpdate.Core.Tests.csproj", "{81B0A72E-04FA-4A4F-856F-68C22FF2D945}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TinyUpdate.MicrosoftStore", "src\Clients\TinyUpdate.MicrosoftStore\TinyUpdate.MicrosoftStore.csproj", "{F364F894-0AF5-4A42-A6D3-9B6A8C10E45B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -72,6 +74,10 @@ Global {81B0A72E-04FA-4A4F-856F-68C22FF2D945}.Debug|Any CPU.Build.0 = Debug|Any CPU {81B0A72E-04FA-4A4F-856F-68C22FF2D945}.Release|Any CPU.ActiveCfg = Release|Any CPU {81B0A72E-04FA-4A4F-856F-68C22FF2D945}.Release|Any CPU.Build.0 = Release|Any CPU + {F364F894-0AF5-4A42-A6D3-9B6A8C10E45B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F364F894-0AF5-4A42-A6D3-9B6A8C10E45B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F364F894-0AF5-4A42-A6D3-9B6A8C10E45B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F364F894-0AF5-4A42-A6D3-9B6A8C10E45B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {4B3D4412-9AF3-4D5A-B013-3462B46E815F} = {63F2556D-A89A-4552-84C7-D2BE30718485} @@ -87,5 +93,6 @@ Global {59769ABE-E512-43D0-B85B-37EF133DA27D} = {FFCB9AB6-92FE-48B0-9B93-A5BBF0DB153B} {B5BC1A21-5A4C-403E-918B-CE48675E6F89} = {0BD356C4-B4F2-45D6-A94F-77ED1ECEEE11} {81B0A72E-04FA-4A4F-856F-68C22FF2D945} = {0BD356C4-B4F2-45D6-A94F-77ED1ECEEE11} + {F364F894-0AF5-4A42-A6D3-9B6A8C10E45B} = {EE289D98-EE1C-4D17-9BB0-B4981E26A49D} EndGlobalSection EndGlobal diff --git a/src/Clients/TinyUpdate.MicrosoftStore/MicrosoftStoreClient.cs b/src/Clients/TinyUpdate.MicrosoftStore/MicrosoftStoreClient.cs new file mode 100644 index 0000000..e87940f --- /dev/null +++ b/src/Clients/TinyUpdate.MicrosoftStore/MicrosoftStoreClient.cs @@ -0,0 +1,66 @@ +using Windows.Services.Store; +using Microsoft.Extensions.Logging; +using TinyUpdate.Core.Abstract; + +namespace TinyUpdate.MicrosoftStore; + +public class MicrosoftStoreClient(StoreContext storeContext, ILogger logger) : IPackageClient +{ + public async Task> GetUpdates() + { + var storePackageUpdates = await storeContext.GetAppAndOptionalStorePackageUpdatesAsync(); + return [new MicrosoftStoreReleaseEntry(storePackageUpdates)]; + } + + public async Task DownloadUpdate(ReleaseEntry releaseEntry, IProgress? progress) + { + if (releaseEntry is not MicrosoftStoreReleaseEntry microsoftStoreReleaseEntry) + { + logger.LogError("Wasn't given a microsoft store update entry"); + return false; + } + + var downloadOperation = storeContext.RequestDownloadStorePackageUpdatesAsync(microsoftStoreReleaseEntry + .StorePackageUpdates); + + downloadOperation.Progress = (_, progressStatus) => progress?.Report(progressStatus.PackageDownloadProgress); + + var downloadResult = await downloadOperation.AsTask(); + return downloadResult.OverallState == StorePackageUpdateState.Completed; + } + + public async Task ApplyUpdate(ReleaseEntry releaseEntry, IProgress? progress) + { + //No need to actually do an update, report as successful + if (!releaseEntry.HasUpdate) + { + progress?.Report(1); + return true; + } + + if (releaseEntry is not MicrosoftStoreReleaseEntry microsoftStoreReleaseEntry) + { + logger.LogError("Wasn't given a microsoft store update entry"); + return false; + } + + //TODO: Find out if the 0.8 - 1 applies here? + var installOperation = storeContext.RequestDownloadAndInstallStorePackageUpdatesAsync(microsoftStoreReleaseEntry + .StorePackageUpdates); + + installOperation.Progress = (_, progressStatus) => progress?.Report(progressStatus.TotalDownloadProgress); + var installResult = await installOperation.AsTask(); + return installResult.OverallState == StorePackageUpdateState.Completed; + } +} + +public class MicrosoftStoreReleaseEntry : ReleaseEntry +{ + public MicrosoftStoreReleaseEntry(IReadOnlyList? storePackageUpdates) + { + StorePackageUpdates = storePackageUpdates ?? ArraySegment.Empty; + } + + public IReadOnlyList StorePackageUpdates { get; } + public override bool HasUpdate => StorePackageUpdates.Count > 0; +} \ No newline at end of file diff --git a/src/Clients/TinyUpdate.MicrosoftStore/TinyUpdate.MicrosoftStore.csproj b/src/Clients/TinyUpdate.MicrosoftStore/TinyUpdate.MicrosoftStore.csproj new file mode 100644 index 0000000..19d932d --- /dev/null +++ b/src/Clients/TinyUpdate.MicrosoftStore/TinyUpdate.MicrosoftStore.csproj @@ -0,0 +1,13 @@ + + + + net8.0-windows10.0.17763.0 + enable + enable + + + + + + + diff --git a/src/Deltas/TinyUpdate.Delta.BSDiff/BSDiffDelta.cs b/src/Deltas/TinyUpdate.Delta.BSDiff/BSDiffDelta.cs index 473058d..e7c14f6 100644 --- a/src/Deltas/TinyUpdate.Delta.BSDiff/BSDiffDelta.cs +++ b/src/Deltas/TinyUpdate.Delta.BSDiff/BSDiffDelta.cs @@ -1,8 +1,7 @@ -using System.Buffers.Binary; using Microsoft.Extensions.Logging; using SharpCompress.Compressors; using SharpCompress.Compressors.BZip2; -using TinyUpdate.Core.Abstract; +using TinyUpdate.Core.Abstract.Delta; //TODO: Bring in changes from bsdiff.net into here // Squirrel.Bsdiff: Adapted from https://github.com/LogosBible/bsdiff.net/blob/master/src/bsdiff/BinaryPatchUtility.cs @@ -94,7 +93,8 @@ public async Task ApplyDeltaFile(Stream sourceStream, Stream deltaStream, return false; } - var patchMemoryStream = new MemoryStream(); + //TODO: Check stream type passed + await using var patchMemoryStream = new MemoryStream(); await deltaStream.CopyToAsync(patchMemoryStream); /* @@ -115,7 +115,8 @@ with control block a set of triples (x,y,z) meaning "add x bytes long controlLength, diffLength, newSize; await using (var patchStream = CreatePatchStream()) { - var header = patchStream.ReadExactly(CHeaderSize); + var header = new byte[CHeaderSize]; + patchStream.ReadExactly(header); // check for appropriate magic var signature = ReadInt64(header, 0); @@ -450,7 +451,8 @@ private static bool SupportedStream(Stream deltaStream, out long newSize) return false; } - var header = deltaStream.ReadExactly(CHeaderSize); + var header = new byte[CHeaderSize]; + deltaStream.ReadExactly(header); // check for appropriate magic var signature = ReadInt64(header, 0); @@ -675,14 +677,32 @@ private static int[] SuffixSort(byte[] oldData) private static long ReadInt64(byte[] buf, int offset) { - var value = BinaryPrimitives.ReadInt64LittleEndian(buf); - var mask = value >> 63; - return (~mask & value) | (((value & unchecked((long) 0x8000_0000_0000_0000)) - value) & mask); - } + long value = buf[offset + 7] & 0x7F; + + for (var index = 6; index >= 0; index--) + { + value *= 256; + value += buf[offset + index]; + } + + if ((buf[offset + 7] & 0x80) != 0) + value = -value; + return value; + } + private static void WriteInt64(long value, byte[] buf, int offset) { - var mask = value >> 63; - BinaryPrimitives.WriteInt64LittleEndian(buf, ((value + mask) ^ mask) | (value & unchecked((long) 0x8000_0000_0000_0000))); + var valueToWrite = value < 0 ? -value : value; + + for (var byteIndex = 0; byteIndex < 8; byteIndex++) + { + buf[offset + byteIndex] = (byte)(valueToWrite % 256); + valueToWrite -= buf[offset + byteIndex]; + valueToWrite /= 256; + } + + if (value < 0) + buf[offset + 7] |= 0x80; } } \ No newline at end of file diff --git a/src/Deltas/TinyUpdate.Delta.BSDiff/StreamUtility.cs b/src/Deltas/TinyUpdate.Delta.BSDiff/StreamUtility.cs deleted file mode 100644 index 6f70dc0..0000000 --- a/src/Deltas/TinyUpdate.Delta.BSDiff/StreamUtility.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace TinyUpdate.Delta.BSDiff; - -/// -/// Provides helper methods for working with . -/// -internal static class StreamUtility -{ - /// - /// Reads exactly bytes from . - /// - /// The stream to read from. - /// The count of bytes to read. - /// A new byte array containing the data read from the stream. - public static byte[] ReadExactly(this Stream stream, int count) - { - ArgumentOutOfRangeException.ThrowIfNegative(count); - - var buffer = new byte[count]; - stream.ReadExactly(buffer, 0, count); - return buffer; - } -} \ No newline at end of file diff --git a/src/Deltas/TinyUpdate.Delta.MSDelta/MSDelta.cs b/src/Deltas/TinyUpdate.Delta.MSDelta/MSDelta.cs index 7787e6d..013fac3 100644 --- a/src/Deltas/TinyUpdate.Delta.MSDelta/MSDelta.cs +++ b/src/Deltas/TinyUpdate.Delta.MSDelta/MSDelta.cs @@ -1,6 +1,5 @@ using System.Runtime.InteropServices; -using System.Text; -using TinyUpdate.Core.Abstract; +using TinyUpdate.Core.Abstract.Delta; using TinyUpdate.Delta.MSDelta.Enum; using TinyUpdate.Delta.MSDelta.Struct; diff --git a/src/Packages/TinyUpdate.TUUP/FileEntryExt.cs b/src/Packages/TinyUpdate.TUUP/FileEntryExt.cs index fb24ece..9f32e09 100644 --- a/src/Packages/TinyUpdate.TUUP/FileEntryExt.cs +++ b/src/Packages/TinyUpdate.TUUP/FileEntryExt.cs @@ -1,4 +1,3 @@ -using TinyUpdate.Core; using TinyUpdate.Core.Model; namespace TinyUpdate.TUUP; diff --git a/src/Packages/TinyUpdate.TUUP/TuupUpdatePackage.cs b/src/Packages/TinyUpdate.TUUP/TuupUpdatePackage.cs index 5333e5f..460f25d 100644 --- a/src/Packages/TinyUpdate.TUUP/TuupUpdatePackage.cs +++ b/src/Packages/TinyUpdate.TUUP/TuupUpdatePackage.cs @@ -1,6 +1,6 @@ using System.IO.Compression; -using TinyUpdate.Core; using TinyUpdate.Core.Abstract; +using TinyUpdate.Core.Abstract.Delta; using TinyUpdate.Core.Model; namespace TinyUpdate.TUUP; @@ -18,10 +18,10 @@ public class TuupUpdatePackage(IDeltaManager deltaManager, IHasher hasher) : IUp private ZipArchive? _zipArchive; public string Extension => Consts.TuupExtension; - public ICollection DeltaFiles { get; } = new List(); - public ICollection UnchangedFiles { get; } = new List(); - public ICollection NewFiles { get; } = new List(); - public ICollection MovedFiles { get; } = new List(); + public IReadOnlyCollection DeltaFiles { get; private set; } = ArraySegment.Empty; + public IReadOnlyCollection UnchangedFiles { get; private set; } = ArraySegment.Empty; + public IReadOnlyCollection NewFiles { get; private set; } = ArraySegment.Empty; + public IReadOnlyCollection MovedFiles { get; private set; } = ArraySegment.Empty; public async Task Load(Stream updatePackageStream) { @@ -33,31 +33,39 @@ public async Task Load(Stream updatePackageStream) { throw new InvalidDataException("TuupUpdatePackage expects a zip formatted package", e); } - + + var deltaFiles = new List(); + var unchangedFiles = new List(); + var newFiles = new List(); + var movedFiles = new List(); await foreach (var fileEntry in GetFilesFromPackage(_zipArchive)) { //Add to the correct collection if (fileEntry.IsDeltaFile()) { - DeltaFiles.Add(fileEntry); + deltaFiles.Add(fileEntry); continue; } if (fileEntry.IsNewFile()) { - NewFiles.Add(fileEntry); + newFiles.Add(fileEntry); continue; } if (fileEntry.HasFileMoved()) { - MovedFiles.Add(fileEntry); + movedFiles.Add(fileEntry); continue; } - UnchangedFiles.Add(fileEntry); + unchangedFiles.Add(fileEntry); } + DeltaFiles = deltaFiles; + UnchangedFiles = unchangedFiles; + NewFiles = newFiles; + MovedFiles = movedFiles; _loaded = true; } @@ -67,7 +75,7 @@ public async Task Load(Stream updatePackageStream) /// that contains all the files private async IAsyncEnumerable GetFilesFromPackage(ZipArchive zip) { - var fileEntriesData = new Dictionary>(zip.Entries.Count); + var fileEntriesData = new Dictionary>(zip.Entries.Count / 2); foreach (var zipEntry in zip.Entries) { //Check if the name contains a extension diff --git a/src/Packages/TinyUpdate.TUUP/TuupUpdatePackageCreator.cs b/src/Packages/TinyUpdate.TUUP/TuupUpdatePackageCreator.cs index ef3378a..2081c92 100644 --- a/src/Packages/TinyUpdate.TUUP/TuupUpdatePackageCreator.cs +++ b/src/Packages/TinyUpdate.TUUP/TuupUpdatePackageCreator.cs @@ -3,13 +3,14 @@ using NeoSmart.AsyncLock; using SemVersion; using TinyUpdate.Core.Abstract; +using TinyUpdate.Core.Abstract.Delta; namespace TinyUpdate.TUUP; /// /// Update package creator for /// -public class TuupUpdatePackageCreator : IUpdatePackageCreator +public class TuupUpdatePackageCreator : IDeltaUpdatePackageCreator, IUpdatePackageCreator { private readonly AsyncLock _zipLock; private readonly IHasher _hasher; @@ -151,17 +152,17 @@ async Task AddMovedPathFile(string newFilePath, string previousFileLocation) async Task GetHashes(IDictionary> hashes, IEnumerable files) { //We want to get a hash of all the old files, this allows us to detect files which have moved - foreach (var oldFile in files) + foreach (var fileLocation in files) { - await using var previousFileContentStream = File.OpenRead(oldFile); - var hash = _hasher.HashData(previousFileContentStream); + await using var fileContentStream = File.OpenRead(fileLocation); + var hash = _hasher.HashData(fileContentStream); if (!hashes.TryGetValue(hash, out var filesList)) { filesList = new List(); hashes.Add(hash, filesList); } - filesList.Add(oldFile); + filesList.Add(fileLocation); } } @@ -202,7 +203,7 @@ private async Task AddFile(ZipArchive zipArchive, Stream fileContentStream return true; } - private void CheckFilePath(ref string filepath) + private static void CheckFilePath(ref string filepath) { if (Path.DirectorySeparatorChar != '\\') { diff --git a/src/TinyUpdate.Core/Abstract/IDeltaApplier.cs b/src/TinyUpdate.Core/Abstract/Delta/IDeltaApplier.cs similarity index 95% rename from src/TinyUpdate.Core/Abstract/IDeltaApplier.cs rename to src/TinyUpdate.Core/Abstract/Delta/IDeltaApplier.cs index 0fbf2eb..3b1e4a1 100644 --- a/src/TinyUpdate.Core/Abstract/IDeltaApplier.cs +++ b/src/TinyUpdate.Core/Abstract/Delta/IDeltaApplier.cs @@ -1,4 +1,4 @@ -namespace TinyUpdate.Core.Abstract; +namespace TinyUpdate.Core.Abstract.Delta; /// /// Provides base functionality for applying delta updates diff --git a/src/TinyUpdate.Core/Abstract/IDeltaCreation.cs b/src/TinyUpdate.Core/Abstract/Delta/IDeltaCreation.cs similarity index 93% rename from src/TinyUpdate.Core/Abstract/IDeltaCreation.cs rename to src/TinyUpdate.Core/Abstract/Delta/IDeltaCreation.cs index da13999..61be70d 100644 --- a/src/TinyUpdate.Core/Abstract/IDeltaCreation.cs +++ b/src/TinyUpdate.Core/Abstract/Delta/IDeltaCreation.cs @@ -1,4 +1,4 @@ -namespace TinyUpdate.Core.Abstract; +namespace TinyUpdate.Core.Abstract.Delta; /// /// Provides base functionality for creating delta updates diff --git a/src/TinyUpdate.Core/Abstract/IDeltaManager.cs b/src/TinyUpdate.Core/Abstract/Delta/IDeltaManager.cs similarity index 95% rename from src/TinyUpdate.Core/Abstract/IDeltaManager.cs rename to src/TinyUpdate.Core/Abstract/Delta/IDeltaManager.cs index f9a0ad9..dd6023d 100644 --- a/src/TinyUpdate.Core/Abstract/IDeltaManager.cs +++ b/src/TinyUpdate.Core/Abstract/Delta/IDeltaManager.cs @@ -1,6 +1,6 @@ using TinyUpdate.Core.Model; -namespace TinyUpdate.Core.Abstract; +namespace TinyUpdate.Core.Abstract.Delta; /// /// Manages delta processing for external packages diff --git a/src/TinyUpdate.Core/Abstract/Delta/IDeltaUpdatePackageCreator.cs b/src/TinyUpdate.Core/Abstract/Delta/IDeltaUpdatePackageCreator.cs new file mode 100644 index 0000000..3aa325f --- /dev/null +++ b/src/TinyUpdate.Core/Abstract/Delta/IDeltaUpdatePackageCreator.cs @@ -0,0 +1,27 @@ +using SemVersion; + +namespace TinyUpdate.Core.Abstract.Delta; + +/// +/// Provides base functionality for creating delta update packages +/// +public interface IDeltaUpdatePackageCreator : IExtension +{ + /// + /// Template for a delta package filename (Null if not supported) + /// + string DeltaPackageFilenameTemplate { get; } + + /// + /// Creates a delta update package + /// + /// Where the previous version of the application is located + /// What the previous version of the application is + /// Where the new version of the application is located + /// What the new version of the application is + /// Where we should store the created update package + /// The applications name + /// Process about creating the update package + /// If we successfully created the update package + Task CreateDeltaPackage(string previousApplicationLocation, SemanticVersion previousApplicationVersion, string newApplicationLocation, SemanticVersion newApplicationVersion, string updatePackageLocation, string applicationName, IProgress? progress = null); +} \ No newline at end of file diff --git a/src/TinyUpdate.Core/Abstract/IPackageClient.cs b/src/TinyUpdate.Core/Abstract/IPackageClient.cs new file mode 100644 index 0000000..e729815 --- /dev/null +++ b/src/TinyUpdate.Core/Abstract/IPackageClient.cs @@ -0,0 +1,28 @@ +namespace TinyUpdate.Core.Abstract; + +/// +/// Handles getting updates from a source +/// +public interface IPackageClient +{ + /// + /// Gets all the release entries which can be applied to the application + /// + Task> GetUpdates(); + + /// + /// Downloads a single update + /// + /// Entry which contains data about release + /// Progress of downloading update + /// If downloading the update was successful + Task DownloadUpdate(ReleaseEntry releaseEntry, IProgress? progress); + + /// + /// Applies a single update + /// + /// Entry which contains data about release + /// Progress of applying update + /// If applying the update was successful + Task ApplyUpdate(ReleaseEntry releaseEntry, IProgress? progress); +} \ No newline at end of file diff --git a/src/TinyUpdate.Core/Abstract/IUpdatePackage.cs b/src/TinyUpdate.Core/Abstract/IUpdatePackage.cs index fe265ff..33a823c 100644 --- a/src/TinyUpdate.Core/Abstract/IUpdatePackage.cs +++ b/src/TinyUpdate.Core/Abstract/IUpdatePackage.cs @@ -3,7 +3,7 @@ namespace TinyUpdate.Core.Abstract; /// -/// Provides base functionality for handling an update package +/// Provides base functionality for handling update packages /// public interface IUpdatePackage : IExtension { @@ -16,20 +16,20 @@ public interface IUpdatePackage : IExtension /// /// Files that have been processed into a delta file /// - ICollection DeltaFiles { get; } + IReadOnlyCollection DeltaFiles { get; } /// /// Files that should already be on the device /// - ICollection UnchangedFiles { get; } + IReadOnlyCollection UnchangedFiles { get; } /// /// Files that aren't in the last update /// - ICollection NewFiles { get; } + IReadOnlyCollection NewFiles { get; } /// /// Files that are unchanged but moved /// - ICollection MovedFiles { get; } + IReadOnlyCollection MovedFiles { get; } } \ No newline at end of file diff --git a/src/TinyUpdate.Core/Abstract/IUpdatePackageCreator.cs b/src/TinyUpdate.Core/Abstract/IUpdatePackageCreator.cs index b449ebd..4789bd1 100644 --- a/src/TinyUpdate.Core/Abstract/IUpdatePackageCreator.cs +++ b/src/TinyUpdate.Core/Abstract/IUpdatePackageCreator.cs @@ -3,19 +3,14 @@ namespace TinyUpdate.Core.Abstract; /// -/// Provides base functionality for creating an update package +/// Provides base functionality for creating update packages /// public interface IUpdatePackageCreator : IExtension { /// /// Template for a full package filename /// - public string FullPackageFilenameTemplate { get; } - - /// - /// Template for a delta package filename - /// - public string DeltaPackageFilenameTemplate { get; } + string FullPackageFilenameTemplate { get; } /// /// Creates a full update package @@ -27,17 +22,4 @@ public interface IUpdatePackageCreator : IExtension /// Process about creating the update package /// If we successfully created the update package Task CreateFullPackage(string applicationLocation, SemanticVersion applicationVersion, string updatePackageLocation, string applicationName, IProgress? progress = null); - - /// - /// Creates a delta update package - /// - /// Where the previous version of the application is located - /// What the previous version of the application is - /// Where the new version of the application is located - /// What the new version of the application is - /// Where we should store the created update package - /// The applications name - /// Process about creating the update package - /// If we successfully created the update package - Task CreateDeltaPackage(string previousApplicationLocation, SemanticVersion previousApplicationVersion, string newApplicationLocation, SemanticVersion newApplicationVersion, string updatePackageLocation, string applicationName, IProgress? progress = null); } \ No newline at end of file diff --git a/src/TinyUpdate.Core/Abstract/ReleaseEntry.cs b/src/TinyUpdate.Core/Abstract/ReleaseEntry.cs new file mode 100644 index 0000000..b345485 --- /dev/null +++ b/src/TinyUpdate.Core/Abstract/ReleaseEntry.cs @@ -0,0 +1,12 @@ +namespace TinyUpdate.Core.Abstract; + +/// +/// Provides data about a release +/// +public abstract class ReleaseEntry +{ + /// + /// If this entry contains an update to be applied + /// + public abstract bool HasUpdate { get; } +} \ No newline at end of file diff --git a/src/TinyUpdate.Core/DeltaManager.cs b/src/TinyUpdate.Core/DeltaManager.cs index ec7b61b..d39ae75 100644 --- a/src/TinyUpdate.Core/DeltaManager.cs +++ b/src/TinyUpdate.Core/DeltaManager.cs @@ -1,11 +1,14 @@ using System.Collections.Concurrent; using System.Collections.Immutable; using NeoSmart.AsyncLock; -using TinyUpdate.Core.Abstract; +using TinyUpdate.Core.Abstract.Delta; using TinyUpdate.Core.Model; namespace TinyUpdate.Core; +/// +/// Default implementation +/// public class DeltaManager(IEnumerable appliers, IEnumerable creators) : IDeltaManager { @@ -16,6 +19,9 @@ public class DeltaManager(IEnumerable appliers, IEnumerable CreateDeltaUpdate(Stream sourceStream, Stream targetStream) { + /*As we'll be using multiple creators at the same time, we want to copy the streams here + and then within the for each below, this is so we're not consistently hitting IO*/ + //TODO: Add some checks for the stream type passed? var resultBag = new ConcurrentBag(); var sourceStreamMasterCopy = new MemoryStream(); var targetStreamMasterCopy = new MemoryStream(); @@ -29,6 +35,7 @@ await Parallel.ForEachAsync(Creators, async (creator, token) => var sourceStreamLocalCopy = new MemoryStream(); var targetStreamLocalCopy = new MemoryStream(); + //Copy the master copy into this local copy, otherwise multiple creators would clash when using the same stream using (await _copyLock.LockAsync(token)) { sourceStreamMasterCopy.Seek(0, SeekOrigin.Begin); diff --git a/src/TinyUpdate.Core/Model/DeltaCreationResult.cs b/src/TinyUpdate.Core/Model/DeltaCreationResult.cs index 48a65af..8429865 100644 --- a/src/TinyUpdate.Core/Model/DeltaCreationResult.cs +++ b/src/TinyUpdate.Core/Model/DeltaCreationResult.cs @@ -1,5 +1,5 @@ using System.Diagnostics.CodeAnalysis; -using TinyUpdate.Core.Abstract; +using TinyUpdate.Core.Abstract.Delta; namespace TinyUpdate.Core.Model; diff --git a/src/TinyUpdate.Core/SHA256.cs b/src/TinyUpdate.Core/SHA256.cs index 5b34ed6..d1e7081 100644 --- a/src/TinyUpdate.Core/SHA256.cs +++ b/src/TinyUpdate.Core/SHA256.cs @@ -11,6 +11,9 @@ public partial class SHA256 : IHasher private const string EmptyHash = "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855"; private static readonly Regex Sha256Regex = MyRegex(); + /// + /// Static instance, use this when registering + /// public static readonly SHA256 Instance = new SHA256(); public bool CompareHash(Stream stream, string expectedHash) @@ -47,35 +50,35 @@ public string HashData(Stream stream) return HashData(dataHashed, false); } - public string HashData(byte[] bytes) => HashData(bytes, true); + public string HashData(byte[] byteArray) => HashData(byteArray, true); public bool IsValidHash(string hash) => !string.IsNullOrWhiteSpace(hash) && Sha256Regex.IsMatch(hash); [GeneratedRegex("^[a-fA-F0-9]{64}$", RegexOptions.Compiled)] private static partial Regex MyRegex(); - private static string HashData(byte[] bytes, bool processBytes) + private static string HashData(byte[] byteArray, bool processBytes) { if (processBytes) { //If we got nothing then return this, this will always be calculated by below - if (bytes.Length == 0) + if (byteArray.Length == 0) { return EmptyHash; } - using var memStream = new MemoryStream(bytes); - bytes = System.Security.Cryptography.SHA256.HashData(memStream); + using var memoryStream = new MemoryStream(byteArray); + byteArray = System.Security.Cryptography.SHA256.HashData(memoryStream); } - var resultArray = new Span(new char[64]); + var resultSpan = new Span(new char[64]); var charsWritten = 0; - foreach (var @byte in bytes) + foreach (var @byte in byteArray) { - @byte.TryFormat(resultArray[charsWritten..], out var written, "X2"); + @byte.TryFormat(resultSpan[charsWritten..], out var written, "X2"); charsWritten += written; } - return resultArray.ToString(); + return resultSpan.ToString(); } } \ No newline at end of file diff --git a/tests/TinyUpdate.Core.Tests/Attributes/DeltaApplierAttribute.cs b/tests/TinyUpdate.Core.Tests/Attributes/DeltaApplierAttribute.cs index 2bb620c..a66e30c 100644 --- a/tests/TinyUpdate.Core.Tests/Attributes/DeltaApplierAttribute.cs +++ b/tests/TinyUpdate.Core.Tests/Attributes/DeltaApplierAttribute.cs @@ -1,4 +1,4 @@ -using TinyUpdate.Core.Abstract; +using TinyUpdate.Core.Abstract.Delta; namespace TinyUpdate.Core.Tests.Attributes; diff --git a/tests/TinyUpdate.Core.Tests/Attributes/DeltaCreationAttribute.cs b/tests/TinyUpdate.Core.Tests/Attributes/DeltaCreationAttribute.cs index 599dae4..f75d14a 100644 --- a/tests/TinyUpdate.Core.Tests/Attributes/DeltaCreationAttribute.cs +++ b/tests/TinyUpdate.Core.Tests/Attributes/DeltaCreationAttribute.cs @@ -1,4 +1,4 @@ -using TinyUpdate.Core.Abstract; +using TinyUpdate.Core.Abstract.Delta; namespace TinyUpdate.Core.Tests.Attributes; diff --git a/tests/TinyUpdate.Core.Tests/TestSources/SHA256TestSource.cs b/tests/TinyUpdate.Core.Tests/TestSources/SHA256TestSource.cs index 3d14129..ddc8d11 100644 --- a/tests/TinyUpdate.Core.Tests/TestSources/SHA256TestSource.cs +++ b/tests/TinyUpdate.Core.Tests/TestSources/SHA256TestSource.cs @@ -1,6 +1,4 @@ -using System.Collections; - -namespace TinyUpdate.Core.Tests.TestSources; +namespace TinyUpdate.Core.Tests.TestSources; public static class SHA256TestSource { diff --git a/tests/TinyUpdate.Delta.Tests/Abstract/DeltaCan.cs b/tests/TinyUpdate.Delta.Tests/Abstract/DeltaCan.cs index 74ec5a4..72d97fc 100644 --- a/tests/TinyUpdate.Delta.Tests/Abstract/DeltaCan.cs +++ b/tests/TinyUpdate.Delta.Tests/Abstract/DeltaCan.cs @@ -4,6 +4,7 @@ using System.Runtime.InteropServices; using TinyUpdate.Core; using TinyUpdate.Core.Abstract; +using TinyUpdate.Core.Abstract.Delta; using TinyUpdate.Core.Tests; using TinyUpdate.Core.Tests.Attributes; diff --git a/tests/TinyUpdate.Delta.Tests/Abstract/DeltaManagerCan.cs b/tests/TinyUpdate.Delta.Tests/Abstract/DeltaManagerCan.cs index d8a66ce..d3793ce 100644 --- a/tests/TinyUpdate.Delta.Tests/Abstract/DeltaManagerCan.cs +++ b/tests/TinyUpdate.Delta.Tests/Abstract/DeltaManagerCan.cs @@ -1,7 +1,6 @@ using System.IO.Abstractions; using Moq; -using TinyUpdate.Core; -using TinyUpdate.Core.Abstract; +using TinyUpdate.Core.Abstract.Delta; using TinyUpdate.Core.Model; using TinyUpdate.Core.Tests; using TinyUpdate.Core.Tests.Attributes; diff --git a/tests/TinyUpdate.Delta.Tests/DeltaManagerTests.cs b/tests/TinyUpdate.Delta.Tests/DeltaManagerTests.cs index 7812d46..0f92f21 100644 --- a/tests/TinyUpdate.Delta.Tests/DeltaManagerTests.cs +++ b/tests/TinyUpdate.Delta.Tests/DeltaManagerTests.cs @@ -1,5 +1,5 @@ using TinyUpdate.Core; -using TinyUpdate.Core.Abstract; +using TinyUpdate.Core.Abstract.Delta; using TinyUpdate.Delta.Tests.Abstract; namespace TinyUpdate.Delta.Tests; diff --git a/tests/TinyUpdate.Packages.Tests/Abstract/UpdatePackageCan.cs b/tests/TinyUpdate.Packages.Tests/Abstract/UpdatePackageCan.cs index 4ea6b77..eaef579 100644 --- a/tests/TinyUpdate.Packages.Tests/Abstract/UpdatePackageCan.cs +++ b/tests/TinyUpdate.Packages.Tests/Abstract/UpdatePackageCan.cs @@ -3,8 +3,8 @@ using System.Text.Json; using Moq; using SemVersion; -using TinyUpdate.Core; using TinyUpdate.Core.Abstract; +using TinyUpdate.Core.Abstract.Delta; using TinyUpdate.Core.Model; using TinyUpdate.Core.Tests; using TinyUpdate.Core.Tests.Attributes; @@ -15,7 +15,8 @@ namespace TinyUpdate.Packages.Tests.Abstract; public abstract class UpdatePackageCan { protected IUpdatePackage UpdatePackage = null!; - protected IUpdatePackageCreator UpdatePackageCreator = null!; + protected IDeltaUpdatePackageCreator DeltaPackageCreator = null!; + protected IUpdatePackageCreator FullPackageCreator = null!; protected IFileSystem FileSystem = null!; [OneTimeSetUp] @@ -66,7 +67,7 @@ await Assert.MultipleAsync(async () => return JsonSerializer.Deserialize>(await File.ReadAllTextAsync(fileLocation)); } - void CheckEntries(IReadOnlyCollection expectedCollection, ICollection actualCollection, bool shouldHaveStream) + void CheckEntries(IReadOnlyCollection expectedCollection, IReadOnlyCollection actualCollection, bool shouldHaveStream) { Assert.That(expectedCollection, Has.Count.EqualTo(actualCollection.Count)); foreach (var expectedFileEntry in expectedCollection) @@ -100,7 +101,7 @@ public async Task TestFullPackageCreation(FullUpdatePackageTestData testData) FileSystem.Directory.CreateDirectory(packageLocation); - var successful = await UpdatePackageCreator.CreateFullPackage(location, testData.Version, packageLocation, testData.ApplicationName); + var successful = await FullPackageCreator.CreateFullPackage(location, testData.Version, packageLocation, testData.ApplicationName); Assert.That(successful, Is.True); var expectedFileLocation = ExpectedTargetFileLocation(testData.ExpectedFilename); @@ -136,7 +137,7 @@ public async Task TestDeltaPackageCreation(DeltaUpdatePackageTestData testData) FileSystem.Directory.CreateDirectory(packageLocation); - var successful = await UpdatePackageCreator.CreateDeltaPackage(oldLocation, oldVersion, newLocation, testData.NewVersion, packageLocation, testData.ApplicationName); + var successful = await DeltaPackageCreator.CreateDeltaPackage(oldLocation, oldVersion, newLocation, testData.NewVersion, packageLocation, testData.ApplicationName); Assert.That(successful, Is.True); var expectedFileLocation = ExpectedTargetFileLocation(testData.ExpectedFilename); @@ -171,7 +172,7 @@ public async Task MakeFullUpdatePackage() FileSystem.Directory.CreateDirectory(packageLocation); await CreateDirectoryData(location, 1); - var successful = await UpdatePackageCreator.CreateFullPackage(location, version, packageLocation, applicationName); + var successful = await FullPackageCreator.CreateFullPackage(location, version, packageLocation, applicationName); Assert.That(successful, Is.True); //Content checking is done by other tests, we just want to check if we can create a more complex update package } @@ -197,7 +198,7 @@ public async Task MakeDeltaUpdatePackage() CopyDirectory(oldLocation, newLocation); await MessAroundWithDirectory(newLocation); - var successful = await UpdatePackageCreator.CreateDeltaPackage(oldLocation, oldVersion, newLocation, newVersion, packageLocation, applicationName); + var successful = await DeltaPackageCreator.CreateDeltaPackage(oldLocation, oldVersion, newLocation, newVersion, packageLocation, applicationName); Assert.That(successful, Is.True); //Content checking is done by other tests, we just want to check if we can create a more complex update package } @@ -316,22 +317,22 @@ private async Task MakeRandomFile(string file) Functions.FillStreamWithRandomData(fileStream); } - private string UpdatePackageCreatorName => UpdatePackageCreator.GetType().Name; + private string UpdatePackageCreatorName => DeltaPackageCreator.GetType().Name; private Stream GetDeltaTargetStream(string packageLocation, string applicationName, SemanticVersion newVersion) { return FileSystem.File.OpenRead(Path.Combine(packageLocation, - string.Format(UpdatePackageCreator.DeltaPackageFilenameTemplate, applicationName, newVersion))); + string.Format(DeltaPackageCreator.DeltaPackageFilenameTemplate, applicationName, newVersion))); } private Stream GetFullTargetStream(string packageLocation, string applicationName, SemanticVersion newVersion) { return FileSystem.File.OpenRead(Path.Combine(packageLocation, - string.Format(UpdatePackageCreator.FullPackageFilenameTemplate, applicationName, newVersion))); + string.Format(FullPackageCreator.FullPackageFilenameTemplate, applicationName, newVersion))); } private Stream GetExpectedTargetStream(string fileLocation) => FileSystem.File.OpenRead(fileLocation); private string ExpectedTargetFileLocation(string filename) => Path.Combine("Assets", UpdatePackageCreatorName, - filename + UpdatePackageCreator.Extension); + filename + DeltaPackageCreator.Extension); } \ No newline at end of file diff --git a/tests/TinyUpdate.Packages.Tests/UpdatePackageTests.cs b/tests/TinyUpdate.Packages.Tests/UpdatePackageTests.cs index 484476f..ffd35af 100644 --- a/tests/TinyUpdate.Packages.Tests/UpdatePackageTests.cs +++ b/tests/TinyUpdate.Packages.Tests/UpdatePackageTests.cs @@ -27,8 +27,12 @@ public void Setup() [ mockApplier1.Object, mockApplier2.Object ], [ mockCreation1.Object, mockCreation2.Object ]); + var tuupPackageCreator = new TuupUpdatePackageCreator(_sha256Hasher, deltaManager, FileSystem, + new TuupUpdatePackageCreatorOptions()); + UpdatePackage = new TuupUpdatePackage(deltaManager, _sha256Hasher); - UpdatePackageCreator = new TuupUpdatePackageCreator(_sha256Hasher, deltaManager, FileSystem, new TuupUpdatePackageCreatorOptions()); + DeltaPackageCreator = tuupPackageCreator; + FullPackageCreator = tuupPackageCreator; } private static bool NeedsFixedCreatorSize