diff --git a/src/Squirrel/BinaryPatchUtility.cs b/src/Squirrel/BinaryPatchUtility.cs deleted file mode 100644 index e53fa5b44..000000000 --- a/src/Squirrel/BinaryPatchUtility.cs +++ /dev/null @@ -1,922 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Threading; -using ICSharpCode.SharpZipLib.BZip2; - -// Adapted from https://github.com/LogosBible/bsdiff.net/blob/master/src/bsdiff/BinaryPatchUtility.cs - -namespace Squirrel -{ - /* - The original bsdiff.c source code (http://www.daemonology.net/bsdiff/) is - distributed under the following license: - - Copyright 2003-2005 Colin Percival - All rights reserved - - Redistribution and use in source and binary forms, with or without - modification, are permitted providing that the following conditions - are met: - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING - IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. - */ - class BinaryPatchUtility - { - /// - /// Creates a binary patch (in bsdiff format) that can be used - /// (by ) to transform into . - /// - /// The original binary data. - /// The new binary data. - /// A to which the patch will be written. - public static void Create(byte[] oldData, byte[] newData, Stream output) - { - // NB: If you diff a file big enough, we blow the stack. This doesn't - // solve it, just buys us more space. The solution is to rewrite Split - // using iteration instead of recursion, but that's Hard(tm). - var ex = default(Exception); - var t = new Thread(() => { - try { - CreateInternal(oldData, newData, output); - } catch (Exception exc) { - ex = exc; - } - }, 40 * 1048576); - - t.Start(); - t.Join(); - - if (ex != null) throw ex; - } - - static void CreateInternal(byte[] oldData, byte[] newData, Stream output) - { - // check arguments - if (oldData == null) - throw new ArgumentNullException("oldData"); - if (newData == null) - throw new ArgumentNullException("newData"); - if (output == null) - throw new ArgumentNullException("output"); - if (!output.CanSeek) - throw new ArgumentException("Output stream must be seekable.", "output"); - if (!output.CanWrite) - throw new ArgumentException("Output stream must be writable.", "output"); - - /* Header is - 0 8 "BSDIFF40" - 8 8 length of bzip2ed ctrl block - 16 8 length of bzip2ed diff block - 24 8 length of new file */ - /* File is - 0 32 Header - 32 ?? Bzip2ed ctrl block - ?? ?? Bzip2ed diff block - ?? ?? Bzip2ed extra block */ - byte[] header = new byte[c_headerSize]; - WriteInt64(c_fileSignature, header, 0); // "BSDIFF40" - WriteInt64(0, header, 8); - WriteInt64(0, header, 16); - WriteInt64(newData.Length, header, 24); - - long startPosition = output.Position; - output.Write(header, 0, header.Length); - - int[] I = SuffixSort(oldData); - - byte[] db = new byte[newData.Length]; - byte[] eb = new byte[newData.Length]; - - int dblen = 0; - int eblen = 0; - - using (WrappingStream wrappingStream = new WrappingStream(output, Ownership.None)) - using (BZip2OutputStream bz2Stream = new BZip2OutputStream(wrappingStream)) - { - // compute the differences, writing ctrl as we go - int scan = 0; - int pos = 0; - int len = 0; - int lastscan = 0; - int lastpos = 0; - int lastoffset = 0; - while (scan < newData.Length) - { - int oldscore = 0; - - for (int scsc = scan += len; scan < newData.Length; scan++) - { - len = Search(I, oldData, newData, scan, 0, oldData.Length, out pos); - - for (; scsc < scan + len; scsc++) - { - if ((scsc + lastoffset < oldData.Length) && (oldData[scsc + lastoffset] == newData[scsc])) - oldscore++; - } - - if ((len == oldscore && len != 0) || (len > oldscore + 8)) - break; - - if ((scan + lastoffset < oldData.Length) && (oldData[scan + lastoffset] == newData[scan])) - oldscore--; - } - - if (len != oldscore || scan == newData.Length) - { - int s = 0; - int sf = 0; - int lenf = 0; - for (int i = 0; (lastscan + i < scan) && (lastpos + i < oldData.Length); ) - { - if (oldData[lastpos + i] == newData[lastscan + i]) - s++; - i++; - if (s * 2 - i > sf * 2 - lenf) - { - sf = s; - lenf = i; - } - } - - int lenb = 0; - if (scan < newData.Length) - { - s = 0; - int sb = 0; - for (int i = 1; (scan >= lastscan + i) && (pos >= i); i++) - { - if (oldData[pos - i] == newData[scan - i]) - s++; - if (s * 2 - i > sb * 2 - lenb) - { - sb = s; - lenb = i; - } - } - } - - if (lastscan + lenf > scan - lenb) - { - int overlap = (lastscan + lenf) - (scan - lenb); - s = 0; - int ss = 0; - int lens = 0; - for (int i = 0; i < overlap; i++) - { - if (newData[lastscan + lenf - overlap + i] == oldData[lastpos + lenf - overlap + i]) - s++; - if (newData[scan - lenb + i] == oldData[pos - lenb + i]) - s--; - if (s > ss) - { - ss = s; - lens = i + 1; - } - } - - lenf += lens - overlap; - lenb -= lens; - } - - for (int i = 0; i < lenf; i++) - db[dblen + i] = (byte) (newData[lastscan + i] - oldData[lastpos + i]); - for (int i = 0; i < (scan - lenb) - (lastscan + lenf); i++) - eb[eblen + i] = newData[lastscan + lenf + i]; - - dblen += lenf; - eblen += (scan - lenb) - (lastscan + lenf); - - byte[] buf = new byte[8]; - WriteInt64(lenf, buf, 0); - bz2Stream.Write(buf, 0, 8); - - WriteInt64((scan - lenb) - (lastscan + lenf), buf, 0); - bz2Stream.Write(buf, 0, 8); - - WriteInt64((pos - lenb) - (lastpos + lenf), buf, 0); - bz2Stream.Write(buf, 0, 8); - - lastscan = scan - lenb; - lastpos = pos - lenb; - lastoffset = pos - scan; - } - } - } - - // compute size of compressed ctrl data - long controlEndPosition = output.Position; - WriteInt64(controlEndPosition - startPosition - c_headerSize, header, 8); - - // write compressed diff data - using (WrappingStream wrappingStream = new WrappingStream(output, Ownership.None)) - using (BZip2OutputStream bz2Stream = new BZip2OutputStream(wrappingStream)) - { - bz2Stream.Write(db, 0, dblen); - } - - // compute size of compressed diff data - long diffEndPosition = output.Position; - WriteInt64(diffEndPosition - controlEndPosition, header, 16); - - // write compressed extra data - using (WrappingStream wrappingStream = new WrappingStream(output, Ownership.None)) - using (BZip2OutputStream bz2Stream = new BZip2OutputStream(wrappingStream)) - { - bz2Stream.Write(eb, 0, eblen); - } - - // seek to the beginning, write the header, then seek back to end - long endPosition = output.Position; - output.Position = startPosition; - output.Write(header, 0, header.Length); - output.Position = endPosition; - } - - /// - /// Applies a binary patch (in bsdiff format) to the data in - /// and writes the results of patching to . - /// - /// A containing the input data. - /// A func that can open a positioned at the start of the patch data. - /// This stream must support reading and seeking, and must allow multiple streams on - /// the patch to be opened concurrently. - /// A to which the patched data is written. - public static void Apply(Stream input, Func openPatchStream, Stream output) - { - // check arguments - if (input == null) - throw new ArgumentNullException("input"); - if (openPatchStream == null) - throw new ArgumentNullException("openPatchStream"); - if (output == null) - throw new ArgumentNullException("output"); - - /* - File format: - 0 8 "BSDIFF40" - 8 8 X - 16 8 Y - 24 8 sizeof(newfile) - 32 X bzip2(control block) - 32+X Y bzip2(diff block) - 32+X+Y ??? bzip2(extra block) - with control block a set of triples (x,y,z) meaning "add x bytes - from oldfile to x bytes from the diff block; copy y bytes from the - extra block; seek forwards in oldfile by z bytes". - */ - // read header - long controlLength, diffLength, newSize; - using (Stream patchStream = openPatchStream()) - { - // check patch stream capabilities - if (!patchStream.CanRead) - throw new ArgumentException("Patch stream must be readable.", "openPatchStream"); - if (!patchStream.CanSeek) - throw new ArgumentException("Patch stream must be seekable.", "openPatchStream"); - - byte[] header = patchStream.ReadExactly(c_headerSize); - - // check for appropriate magic - long signature = ReadInt64(header, 0); - if (signature != c_fileSignature) - throw new InvalidOperationException("Corrupt patch."); - - // read lengths from header - controlLength = ReadInt64(header, 8); - diffLength = ReadInt64(header, 16); - newSize = ReadInt64(header, 24); - if (controlLength < 0 || diffLength < 0 || newSize < 0) - throw new InvalidOperationException("Corrupt patch."); - } - - // preallocate buffers for reading and writing - const int c_bufferSize = 1048576; - byte[] newData = new byte[c_bufferSize]; - byte[] oldData = new byte[c_bufferSize]; - - // prepare to read three parts of the patch in parallel - using (Stream compressedControlStream = openPatchStream()) - using (Stream compressedDiffStream = openPatchStream()) - using (Stream compressedExtraStream = openPatchStream()) - { - // seek to the start of each part - compressedControlStream.Seek(c_headerSize, SeekOrigin.Current); - compressedDiffStream.Seek(c_headerSize + controlLength, SeekOrigin.Current); - compressedExtraStream.Seek(c_headerSize + controlLength + diffLength, SeekOrigin.Current); - - // decompress each part (to read it) - using (BZip2InputStream controlStream = new BZip2InputStream(compressedControlStream)) - using (BZip2InputStream diffStream = new BZip2InputStream(compressedDiffStream)) - using (BZip2InputStream extraStream = new BZip2InputStream(compressedExtraStream)) - { - long[] control = new long[3]; - byte[] buffer = new byte[8]; - - int oldPosition = 0; - int newPosition = 0; - while (newPosition < newSize) - { - // read control data - for (int i = 0; i < 3; i++) - { - controlStream.ReadExactly(buffer, 0, 8); - control[i] = ReadInt64(buffer, 0); - } - - // sanity-check - if (newPosition + control[0] > newSize) - throw new InvalidOperationException("Corrupt patch."); - - // seek old file to the position that the new data is diffed against - input.Position = oldPosition; - - int bytesToCopy = (int)control[0]; - while (bytesToCopy > 0) - { - int actualBytesToCopy = Math.Min(bytesToCopy, c_bufferSize); - - // read diff string - diffStream.ReadExactly(newData, 0, actualBytesToCopy); - - // add old data to diff string - int availableInputBytes = Math.Min(actualBytesToCopy, (int)(input.Length - input.Position)); - input.ReadExactly(oldData, 0, availableInputBytes); - - for (int index = 0; index < availableInputBytes; index++) - newData[index] += oldData[index]; - - output.Write(newData, 0, actualBytesToCopy); - - // adjust counters - newPosition += actualBytesToCopy; - oldPosition += actualBytesToCopy; - bytesToCopy -= actualBytesToCopy; - } - - // sanity-check - if (newPosition + control[1] > newSize) - throw new InvalidOperationException("Corrupt patch."); - - // read extra string - bytesToCopy = (int)control[1]; - while (bytesToCopy > 0) - { - int actualBytesToCopy = Math.Min(bytesToCopy, c_bufferSize); - - extraStream.ReadExactly(newData, 0, actualBytesToCopy); - output.Write(newData, 0, actualBytesToCopy); - - newPosition += actualBytesToCopy; - bytesToCopy -= actualBytesToCopy; - } - - // adjust position - oldPosition = (int)(oldPosition + control[2]); - } - } - } - } - - private static int CompareBytes(byte[] left, int leftOffset, byte[] right, int rightOffset) - { - for (int index = 0; index < left.Length - leftOffset && index < right.Length - rightOffset; index++) - { - int diff = left[index + leftOffset] - right[index + rightOffset]; - if (diff != 0) - return diff; - } - return 0; - } - - private static int MatchLength(byte[] oldData, int oldOffset, byte[] newData, int newOffset) - { - int i; - for (i = 0; i < oldData.Length - oldOffset && i < newData.Length - newOffset; i++) - { - if (oldData[i + oldOffset] != newData[i + newOffset]) - break; - } - return i; - } - - private static int Search(int[] I, byte[] oldData, byte[] newData, int newOffset, int start, int end, out int pos) - { - if (end - start < 2) - { - int startLength = MatchLength(oldData, I[start], newData, newOffset); - int endLength = MatchLength(oldData, I[end], newData, newOffset); - - if (startLength > endLength) - { - pos = I[start]; - return startLength; - } - else - { - pos = I[end]; - return endLength; - } - } - else - { - int midPoint = start + (end - start) / 2; - return CompareBytes(oldData, I[midPoint], newData, newOffset) < 0 ? - Search(I, oldData, newData, newOffset, midPoint, end, out pos) : - Search(I, oldData, newData, newOffset, start, midPoint, out pos); - } - } - - private static void Split(int[] I, int[] v, int start, int len, int h) - { - if (len < 16) - { - int j; - for (int k = start; k < start + len; k += j) - { - j = 1; - int x = v[I[k] + h]; - for (int i = 1; k + i < start + len; i++) - { - if (v[I[k + i] + h] < x) - { - x = v[I[k + i] + h]; - j = 0; - } - if (v[I[k + i] + h] == x) - { - Swap(ref I[k + j], ref I[k + i]); - j++; - } - } - for (int i = 0; i < j; i++) - v[I[k + i]] = k + j - 1; - if (j == 1) - I[k] = -1; - } - } - else - { - int x = v[I[start + len / 2] + h]; - int jj = 0; - int kk = 0; - for (int i2 = start; i2 < start + len; i2++) - { - if (v[I[i2] + h] < x) - jj++; - if (v[I[i2] + h] == x) - kk++; - } - jj += start; - kk += jj; - - int i = start; - int j = 0; - int k = 0; - while (i < jj) - { - if (v[I[i] + h] < x) - { - i++; - } - else if (v[I[i] + h] == x) - { - Swap(ref I[i], ref I[jj + j]); - j++; - } - else - { - Swap(ref I[i], ref I[kk + k]); - k++; - } - } - - while (jj + j < kk) - { - if (v[I[jj + j] + h] == x) - { - j++; - } - else - { - Swap(ref I[jj + j], ref I[kk + k]); - k++; - } - } - - if (jj > start) - Split(I, v, start, jj - start, h); - - for (i = 0; i < kk - jj; i++) - v[I[jj + i]] = kk - 1; - if (jj == kk - 1) - I[jj] = -1; - - if (start + len > kk) - Split(I, v, kk, start + len - kk, h); - } - } - - private static int[] SuffixSort(byte[] oldData) - { - int[] buckets = new int[256]; - - foreach (byte oldByte in oldData) - buckets[oldByte]++; - for (int i = 1; i < 256; i++) - buckets[i] += buckets[i - 1]; - for (int i = 255; i > 0; i--) - buckets[i] = buckets[i - 1]; - buckets[0] = 0; - - int[] I = new int[oldData.Length + 1]; - for (int i = 0; i < oldData.Length; i++) - I[++buckets[oldData[i]]] = i; - - int[] v = new int[oldData.Length + 1]; - for (int i = 0; i < oldData.Length; i++) - v[i] = buckets[oldData[i]]; - - for (int i = 1; i < 256; i++) - { - if (buckets[i] == buckets[i - 1] + 1) - I[buckets[i]] = -1; - } - I[0] = -1; - - for (int h = 1; I[0] != -(oldData.Length + 1); h += h) - { - int len = 0; - int i = 0; - while (i < oldData.Length + 1) - { - if (I[i] < 0) - { - len -= I[i]; - i -= I[i]; - } - else - { - if (len != 0) - I[i - len] = -len; - len = v[I[i]] + 1 - i; - Split(I, v, i, len, h); - i += len; - len = 0; - } - } - - if (len != 0) - I[i - len] = -len; - } - - for (int i = 0; i < oldData.Length + 1; i++) - I[v[i]] = i; - - return I; - } - - private static void Swap(ref int first, ref int second) - { - int temp = first; - first = second; - second = temp; - } - - private static long ReadInt64(byte[] buf, int offset) - { - long value = buf[offset + 7] & 0x7F; - - for (int 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) - { - long valueToWrite = value < 0 ? -value : value; - - for (int byteIndex = 0; byteIndex < 8; byteIndex++) - { - buf[offset + byteIndex] = (byte)(valueToWrite % 256); - valueToWrite -= buf[offset + byteIndex]; - valueToWrite /= 256; - } - - if (value < 0) - buf[offset + 7] |= 0x80; - } - - const long c_fileSignature = 0x3034464649445342L; - const int c_headerSize = 32; - } - - /// - /// A that wraps another stream. One major feature of is that it does not dispose the - /// underlying stream when it is disposed if Ownership.None is used; this is useful when using classes such as and - /// that take ownership of the stream passed to their constructors. - /// - /// See WrappingStream Implementation. - public class WrappingStream : Stream - { - /// - /// Initializes a new instance of the class. - /// - /// The wrapped stream. - /// Use Owns if the wrapped stream should be disposed when this stream is disposed. - public WrappingStream(Stream streamBase, Ownership ownership) - { - // check parameters - if (streamBase == null) - throw new ArgumentNullException("streamBase"); - - m_streamBase = streamBase; - m_ownership = ownership; - } - - /// - /// Gets a value indicating whether the current stream supports reading. - /// - /// true if the stream supports reading; otherwise, false. - public override bool CanRead - { - get { return m_streamBase == null ? false : m_streamBase.CanRead; } - } - - /// - /// Gets a value indicating whether the current stream supports seeking. - /// - /// true if the stream supports seeking; otherwise, false. - public override bool CanSeek - { - get { return m_streamBase == null ? false : m_streamBase.CanSeek; } - } - - /// - /// Gets a value indicating whether the current stream supports writing. - /// - /// true if the stream supports writing; otherwise, false. - public override bool CanWrite - { - get { return m_streamBase == null ? false : m_streamBase.CanWrite; } - } - - /// - /// Gets the length in bytes of the stream. - /// - public override long Length - { - get { ThrowIfDisposed(); return m_streamBase.Length; } - } - - /// - /// Gets or sets the position within the current stream. - /// - public override long Position - { - get { ThrowIfDisposed(); return m_streamBase.Position; } - set { ThrowIfDisposed(); m_streamBase.Position = value; } - } - - /// - /// Begins an asynchronous read operation. - /// - public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) - { - ThrowIfDisposed(); - return m_streamBase.BeginRead(buffer, offset, count, callback, state); - } - - /// - /// Begins an asynchronous write operation. - /// - public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) - { - ThrowIfDisposed(); - return m_streamBase.BeginWrite(buffer, offset, count, callback, state); - } - - /// - /// Waits for the pending asynchronous read to complete. - /// - public override int EndRead(IAsyncResult asyncResult) - { - ThrowIfDisposed(); - return m_streamBase.EndRead(asyncResult); - } - - /// - /// Ends an asynchronous write operation. - /// - public override void EndWrite(IAsyncResult asyncResult) - { - ThrowIfDisposed(); - m_streamBase.EndWrite(asyncResult); - } - - /// - /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device. - /// - public override void Flush() - { - ThrowIfDisposed(); - m_streamBase.Flush(); - } - - /// - /// Reads a sequence of bytes from the current stream and advances the position - /// within the stream by the number of bytes read. - /// - public override int Read(byte[] buffer, int offset, int count) - { - ThrowIfDisposed(); - return m_streamBase.Read(buffer, offset, count); - } - - /// - /// Reads a byte from the stream and advances the position within the stream by one byte, or returns -1 if at the end of the stream. - /// - public override int ReadByte() - { - ThrowIfDisposed(); - return m_streamBase.ReadByte(); - } - - /// - /// Sets the position within the current stream. - /// - /// A byte offset relative to the parameter. - /// A value of type indicating the reference point used to obtain the new position. - /// The new position within the current stream. - public override long Seek(long offset, SeekOrigin origin) - { - ThrowIfDisposed(); - return m_streamBase.Seek(offset, origin); - } - - /// - /// Sets the length of the current stream. - /// - /// The desired length of the current stream in bytes. - public override void SetLength(long value) - { - ThrowIfDisposed(); - m_streamBase.SetLength(value); - } - - /// - /// Writes a sequence of bytes to the current stream and advances the current position - /// within this stream by the number of bytes written. - /// - public override void Write(byte[] buffer, int offset, int count) - { - ThrowIfDisposed(); - m_streamBase.Write(buffer, offset, count); - } - - /// - /// Writes a byte to the current position in the stream and advances the position within the stream by one byte. - /// - public override void WriteByte(byte value) - { - ThrowIfDisposed(); - m_streamBase.WriteByte(value); - } - - /// - /// Gets the wrapped stream. - /// - /// The wrapped stream. - protected Stream WrappedStream - { - get { return m_streamBase; } - } - - /// - /// Releases the unmanaged resources used by the and optionally releases the managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected override void Dispose(bool disposing) - { - try - { - // doesn't close the base stream, but just prevents access to it through this WrappingStream - if (disposing) - { - if (m_streamBase != null && m_ownership == Ownership.Owns) - m_streamBase.Dispose(); - m_streamBase = null; - } - } - finally - { - base.Dispose(disposing); - } - } - - private void ThrowIfDisposed() - { - // throws an ObjectDisposedException if this object has been disposed - if (m_streamBase == null) - throw new ObjectDisposedException(GetType().Name); - } - - Stream m_streamBase; - readonly Ownership m_ownership; - } - - /// - /// Indicates whether an object takes ownership of an item. - /// - public enum Ownership - { - /// - /// The object does not own this item. - /// - None, - - /// - /// The object owns this item, and is responsible for releasing it. - /// - Owns - } - - /// - /// Provides helper methods for working with . - /// - public 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) - { - if (count < 0) - throw new ArgumentOutOfRangeException("count"); - byte[] buffer = new byte[count]; - ReadExactly(stream, buffer, 0, count); - return buffer; - } - - /// - /// Reads exactly bytes from into - /// , starting at the byte given by . - /// - /// The stream to read from. - /// The buffer to read data into. - /// The offset within the buffer at which data is first written. - /// The count of bytes to read. - public static void ReadExactly(this Stream stream, byte[] buffer, int offset, int count) - { - // check arguments - if (stream == null) - throw new ArgumentNullException("stream"); - if (buffer == null) - throw new ArgumentNullException("buffer"); - if (offset < 0 || offset > buffer.Length) - throw new ArgumentOutOfRangeException("offset"); - if (count < 0 || buffer.Length - offset < count) - throw new ArgumentOutOfRangeException("count"); - - while (count > 0) - { - // read data - int bytesRead = stream.Read(buffer, offset, count); - - // check for failure to read - if (bytesRead == 0) - throw new EndOfStreamException(); - - // move to next block - offset += bytesRead; - count -= bytesRead; - } - } - } -} diff --git a/src/Squirrel/DeltaPackage.cs b/src/Squirrel/DeltaPackage.cs index eb539f4c8..c44e56be9 100644 --- a/src/Squirrel/DeltaPackage.cs +++ b/src/Squirrel/DeltaPackage.cs @@ -7,6 +7,7 @@ using System.Text.RegularExpressions; using ICSharpCode.SharpZipLib.Zip; using Splat; +using DeltaCompressionDotNet.MsDelta; namespace Squirrel { @@ -102,6 +103,7 @@ public ReleasePackage ApplyDeltaPackage(ReleasePackage basePackage, ReleasePacka // Apply all of the .diff files deltaPathRelativePaths .Where(x => x.StartsWith("lib", StringComparison.InvariantCultureIgnoreCase)) + .Where(x => !x.EndsWith(".shasum", StringComparison.InvariantCultureIgnoreCase)) .ForEach(file => { pathsVisited.Add(Regex.Replace(file, @".diff$", "").ToLowerInvariant()); applyDiffToFile(deltaPath, file, workingPath); @@ -164,13 +166,12 @@ void createDeltaForSingleFile(FileInfo targetFile, DirectoryInfo workingDirector } this.Log().Info("Delta patching {0} => {1}", baseFileListing[relativePath], targetFile.FullName); - using (var of = File.Create(targetFile.FullName + ".diff")) { - BinaryPatchUtility.Create(oldData, newData, of); + var msDelta = new MsDeltaCompression(); + msDelta.CreateDelta(baseFileListing[relativePath], targetFile.FullName, targetFile.FullName + ".diff"); - var rl = ReleaseEntry.GenerateFromFile(new MemoryStream(newData), targetFile.Name + ".shasum"); - File.WriteAllText(targetFile.FullName + ".shasum", rl.EntryAsString, Encoding.UTF8); - targetFile.Delete(); - } + var rl = ReleaseEntry.GenerateFromFile(new MemoryStream(newData), targetFile.Name + ".shasum"); + File.WriteAllText(targetFile.FullName + ".shasum", rl.EntryAsString, Encoding.UTF8); + targetFile.Delete(); } @@ -188,11 +189,9 @@ void applyDiffToFile(string deltaPath, string relativeFilePath, string workingDi } if (relativeFilePath.EndsWith(".diff", StringComparison.InvariantCultureIgnoreCase)) { - using (var of = File.OpenWrite(tempTargetFile)) - using (var inf = File.OpenRead(finalTarget)) { - this.Log().Info("Applying Diff to {0}", relativeFilePath); - BinaryPatchUtility.Apply(inf, () => File.OpenRead(inputFile), of); - } + this.Log().Info("Applying Diff to {0}", relativeFilePath); + var msDelta = new MsDeltaCompression(); + msDelta.ApplyDelta(inputFile, finalTarget, tempTargetFile); try { verifyPatchedFile(relativeFilePath, inputFile, tempTargetFile); diff --git a/src/Squirrel/ReleaseEntry.cs b/src/Squirrel/ReleaseEntry.cs index 926535693..6c7a624a0 100644 --- a/src/Squirrel/ReleaseEntry.cs +++ b/src/Squirrel/ReleaseEntry.cs @@ -148,7 +148,7 @@ public static void WriteReleaseFile(IEnumerable releaseEntries, st Contract.Requires(releaseEntries != null && releaseEntries.Any()); Contract.Requires(!String.IsNullOrEmpty(path)); - using (var f = File.OpenWrite(path)) { + using (var f = File.Open(path, FileMode.Create, FileAccess.Write, FileShare.None)) { WriteReleaseFile(releaseEntries, f); } } diff --git a/src/Squirrel/Squirrel.csproj b/src/Squirrel/Squirrel.csproj index 974c8f006..4fbd9d856 100644 --- a/src/Squirrel/Squirrel.csproj +++ b/src/Squirrel/Squirrel.csproj @@ -39,6 +39,12 @@ MinimumRecommendedRules.ruleset + + ..\..\packages\DeltaCompressionDotNet.1.0.0\lib\net45\DeltaCompressionDotNet.dll + + + ..\..\packages\DeltaCompressionDotNet.1.0.0\lib\net45\DeltaCompressionDotNet.MsDelta.dll + ..\..\ext\ICSharpCode.SharpZipLib.dll @@ -79,7 +85,6 @@ Properties\SolutionAssemblyInfo.cs - diff --git a/src/Squirrel/UpdateManager.ApplyReleases.cs b/src/Squirrel/UpdateManager.ApplyReleases.cs index 182efe084..e91aede98 100644 --- a/src/Squirrel/UpdateManager.ApplyReleases.cs +++ b/src/Squirrel/UpdateManager.ApplyReleases.cs @@ -61,7 +61,10 @@ await this.ErrorIfThrows(() => invokePostInstall(newVersion, attemptingFullInsta progress(75); try { - await cleanDeadVersions(newVersion); + var currentVersion = updateInfo.CurrentlyInstalledVersion != null ? + updateInfo.CurrentlyInstalledVersion.Version : null; + + await cleanDeadVersions(currentVersion, newVersion); } catch (Exception ex) { this.Log().WarnException("Failed to clean dead versions, continuing anyways", ex); } @@ -204,10 +207,8 @@ async Task installPackageToAppDir(UpdateInfo updateInfo, ReleaseEntry re var newCurrentVersion = updateInfo.FutureReleaseEntry.Version; - // Perform post-install; clean up the previous version by asking it - // which shortcuts to install, and nuking them. Then, run the app's - // post install and set up shortcuts. - this.ErrorIfThrows(() => runPostInstallAndCleanup(newCurrentVersion, updateInfo.IsBootstrapping)); + this.Log().Info("runPostInstallAndCleanup: starting fixPinnedExecutables"); + this.ErrorIfThrows(() => fixPinnedExecutables(newCurrentVersion)); return target.FullName; } @@ -230,14 +231,6 @@ void copyFileToLocation(FileSystemInfo target, IPackageFile x) }, "Failed to write file: " + target.FullName); } - void runPostInstallAndCleanup(Version newCurrentVersion, bool isBootstrapping) - { - fixPinnedExecutables(newCurrentVersion); - - this.Log().Info("runPostInstallAndCleanup: finished fixPinnedExecutables"); - cleanUpOldVersions(newCurrentVersion); - } - static bool pathIsInFrameworkProfile(IPackageFile packageFile, FrameworkVersion appFrameworkVersion) { if (!packageFile.Path.StartsWith("lib", StringComparison.InvariantCultureIgnoreCase)) { @@ -292,7 +285,7 @@ async Task createFullPackagesFromDeltas(IEnumerable return await createFullPackagesFromDeltas(releasesToApply.Skip(1), entry); } - void cleanUpOldVersions(Version newCurrentVersion) + void cleanUpOldVersions(Version currentlyExecutingVersion, Version newCurrentVersion) { var directory = new DirectoryInfo(rootAppDirectory); if (!directory.Exists) { @@ -300,7 +293,10 @@ void cleanUpOldVersions(Version newCurrentVersion) return; } - foreach (var v in getOldReleases(newCurrentVersion)) { + foreach (var v in getReleases()) { + var version = v.Name.ToVersion(); + if (version == currentlyExecutingVersion || version == newCurrentVersion) continue; + Utility.DeleteDirectoryAtNextReboot(v.FullName); } } @@ -455,7 +451,7 @@ void updateLink(ShellLink shortcut, string[] oldAppDirectories, string newAppPat // directory are "dead" (i.e. already uninstalled, but not deleted), and // we blow them away. This is to make sure that we don't attempt to run // an uninstaller on an already-uninstalled version. - async Task cleanDeadVersions(Version currentVersion) + async Task cleanDeadVersions(Version originalVersion, Version currentVersion, bool forceUninstall = false) { if (currentVersion == null) return; @@ -464,6 +460,12 @@ async Task cleanDeadVersions(Version currentVersion) this.Log().Info("cleanDeadVersions: for version {0}", currentVersion); + string originalVersionFolder = null; + if (originalVersion != null) { + originalVersionFolder = getDirectoryForRelease(originalVersion).Name; + this.Log().Info("cleanDeadVersions: exclude folder {0}", originalVersionFolder); + } + string currentVersionFolder = null; if (currentVersion != null) { currentVersionFolder = getDirectoryForRelease(currentVersion).Name; @@ -476,18 +478,21 @@ async Task cleanDeadVersions(Version currentVersion) // come from here. var toCleanup = di.GetDirectories() .Where(x => x.Name.ToLowerInvariant().Contains("app-")) - .Where(x => x.Name != currentVersionFolder); + .Where(x => x.Name != currentVersionFolder && x.Name != originalVersionFolder); - await toCleanup.ForEachAsync(async x => { - var squirrelApps = SquirrelAwareExecutableDetector.GetAllSquirrelAwareApps(x.FullName); - var args = String.Format("--squirrel-obsolete {0}", x.Name.Replace("app-", "")); + if (forceUninstall == false) { + await toCleanup.ForEachAsync(async x => { + var squirrelApps = SquirrelAwareExecutableDetector.GetAllSquirrelAwareApps(x.FullName); + var args = String.Format("--squirrel-obsolete {0}", x.Name.Replace("app-", "")); - if (squirrelApps.Count > 0) { - // For each app, run the install command in-order and wait - await squirrelApps.ForEachAsync(exe => Utility.InvokeProcessAsync(exe, args), 1 /* at a time */); - } - }); + if (squirrelApps.Count > 0) { + // For each app, run the install command in-order and wait + await squirrelApps.ForEachAsync(exe => Utility.InvokeProcessAsync(exe, args), 1 /* at a time */); + } + }); + } + // Finally, clean up the app-X.Y.Z directories await toCleanup.ForEachAsync(async x => { try { await Utility.DeleteDirectoryWithFallbackToNextReboot(x.FullName); @@ -495,6 +500,23 @@ await toCleanup.ForEachAsync(async x => { this.Log().WarnException("Couldn't delete directory: " + x.FullName, ex); } }); + + // Clean up the packages directory too + var releasesFile = Utility.LocalReleaseFileForAppDir(rootAppDirectory); + var entries = ReleaseEntry.ParseReleaseFile(File.ReadAllText(releasesFile, Encoding.UTF8)); + var pkgDir = Utility.PackageDirectoryForAppDir(rootAppDirectory); + var releaseEntry = default(ReleaseEntry); + + foreach (var entry in entries) { + if (entry.Version == currentVersion) { + releaseEntry = ReleaseEntry.GenerateFromFile(Path.Combine(pkgDir, entry.Filename)); + continue; + } + + File.Delete(Path.Combine(pkgDir, entry.Filename)); + } + + ReleaseEntry.WriteReleaseFile(new[] { releaseEntry }, releasesFile); } internal async Task> updateLocalReleasesFile() @@ -512,13 +534,6 @@ IEnumerable getReleases() .Where(x => x.Name.StartsWith("app-", StringComparison.InvariantCultureIgnoreCase)); } - IEnumerable getOldReleases(Version version) - { - return getReleases() - .Where(x => x.Name.ToVersion() < version) - .ToArray(); - } - DirectoryInfo getDirectoryForRelease(Version releaseVersion) { return new DirectoryInfo(Path.Combine(rootAppDirectory, "app-" + releaseVersion)); diff --git a/src/Squirrel/packages.config b/src/Squirrel/packages.config index eea75f21e..d44aed42a 100644 --- a/src/Squirrel/packages.config +++ b/src/Squirrel/packages.config @@ -1,5 +1,6 @@  + diff --git a/src/Update/Update.csproj b/src/Update/Update.csproj index 67ca0680a..37da1d338 100644 --- a/src/Update/Update.csproj +++ b/src/Update/Update.csproj @@ -35,6 +35,15 @@ + + ..\..\packages\DeltaCompressionDotNet.1.0.0\lib\net45\DeltaCompressionDotNet.dll + + + ..\..\packages\DeltaCompressionDotNet.1.0.0\lib\net45\DeltaCompressionDotNet.MsDelta.dll + + + ..\..\packages\DeltaCompressionDotNet.1.0.0\lib\net45\DeltaCompressionDotNet.PatchApi.dll + ..\..\packages\Microsoft.Web.Xdt.2.1.1\lib\net40\Microsoft.Web.XmlTransform.dll @@ -110,7 +119,7 @@ cd "$(TargetDir)" -"$(SolutionDir)packages\ILRepack.1.25.0\tools\ILRepack.exe" /internalize /out:$(TargetFileName).tmp $(TargetFileName) WpfAnimatedGif.dll ICSharpCode.SharpZipLib.dll Microsoft.Web.XmlTransform.dll Mono.Cecil.dll NuGet.Core.dll Splat.dll Squirrel.dll +"$(SolutionDir)packages\ILRepack.1.25.0\tools\ILRepack.exe" /internalize /out:$(TargetFileName).tmp $(TargetFileName) WpfAnimatedGif.dll ICSharpCode.SharpZipLib.dll Microsoft.Web.XmlTransform.dll Mono.Cecil.dll NuGet.Core.dll Splat.dll DeltaCompressionDotNet.dll DeltaCompressionDotNet.MsDelta.dll Squirrel.dll del "$(TargetFileName)" ren "$(TargetFileName).tmp" "$(TargetFileName)" diff --git a/src/Update/packages.config b/src/Update/packages.config index accdc7801..2be2893e8 100644 --- a/src/Update/packages.config +++ b/src/Update/packages.config @@ -1,5 +1,6 @@  + diff --git a/test/ApplyReleasesTests.cs b/test/ApplyReleasesTests.cs index e956c5c3c..4b71024be 100644 --- a/test/ApplyReleasesTests.cs +++ b/test/ApplyReleasesTests.cs @@ -252,7 +252,6 @@ public async Task ApplyReleasesWithOneReleaseFile() var filesToFind = new[] { new {Name = "NLog.dll", Version = new Version("2.0.0.0")}, new {Name = "NSync.Core.dll", Version = new Version("1.1.0.0")}, - new {Name = Path.Combine("sub", "Ionic.Zip.dll"), Version = new Version("1.9.1.8")}, }; filesToFind.ForEach(x => { @@ -403,7 +402,6 @@ public async Task ApplyReleasesWithDeltaReleases() var filesToFind = new[] { new {Name = "NLog.dll", Version = new Version("2.0.0.0")}, new {Name = "NSync.Core.dll", Version = new Version("1.1.0.0")}, - new {Name = Path.Combine("sub", "Ionic.Zip.dll"), Version = new Version("1.9.1.8")}, }; filesToFind.ForEach(x => { diff --git a/test/DeltaPackageTests.cs b/test/DeltaPackageTests.cs index 1f23174e3..1a40061c4 100644 --- a/test/DeltaPackageTests.cs +++ b/test/DeltaPackageTests.cs @@ -31,16 +31,16 @@ public void ApplyDeltaPackageSmokeTest() result.Version.ShouldEqual(expected.Version); this.Log().Info("Expected file list:"); - expected.GetFiles().Select(x => x.Path).OrderBy(x => x).ForEach(x => this.Log().Info(x)); + var expectedList = expected.GetFiles().Select(x => x.Path).OrderBy(x => x).ToList(); + expectedList.ForEach(x => this.Log().Info(x)); this.Log().Info("Actual file list:"); - result.GetFiles().Select(x => x.Path).OrderBy(x => x).ForEach(x => this.Log().Info(x)); + var actualList = result.GetFiles().Select(x => x.Path).OrderBy(x => x).ToList(); + actualList.ForEach(x => this.Log().Info(x)); - Enumerable.Zip( - expected.GetFiles().Select(x => x.Path).OrderBy(x => x), - result.GetFiles().Select(x => x.Path).OrderBy(x => x), - (e, a) => e == a - ).All(x => x).ShouldBeTrue(); + Enumerable.Zip(expectedList, actualList, (e, a) => e == a) + .All(x => x != false) + .ShouldBeTrue(); } finally { if (File.Exists(outFile)) { File.Delete(outFile); diff --git a/test/DiffTests.cs b/test/DiffTests.cs deleted file mode 100644 index 76495c3ab..000000000 --- a/test/DiffTests.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Squirrel.Tests.TestHelpers; -using Xunit; - -namespace Squirrel.Tests -{ - public class DiffTests - { - [Fact] - public void CreateAtomDiffSmokeTest() - { - var baseFile = IntegrationTestHelper.GetPath("fixtures", "bsdiff", "atom-137.0.exe"); - var newFile = IntegrationTestHelper.GetPath("fixtures", "bsdiff", "atom-137.1.exe"); - - var baseBytes = File.ReadAllBytes(baseFile); - var newBytes = File.ReadAllBytes(newFile); - - var ms = new MemoryStream(); - BinaryPatchUtility.Create(baseBytes, newBytes, ms); - - Assert.True(ms.Length > 100); - } - } -} diff --git a/test/Properties/AssemblyInfo.cs b/test/Properties/AssemblyInfo.cs index 7a85ff301..c587a6062 100644 --- a/test/Properties/AssemblyInfo.cs +++ b/test/Properties/AssemblyInfo.cs @@ -17,4 +17,5 @@ // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("f781bbe0-d19d-41aa-a78b-c689b1943094")] -[assembly: CollectionBehavior(MaxParallelThreads=1, DisableTestParallelization=true)] \ No newline at end of file +[assembly: CollectionBehavior(MaxParallelThreads=1, DisableTestParallelization=true)] +[assembly: AssemblyMetadata("SquirrelAwareVersion", "1")] \ No newline at end of file diff --git a/test/Squirrel.Tests.csproj b/test/Squirrel.Tests.csproj index ba9bb2ac5..1670a210c 100644 --- a/test/Squirrel.Tests.csproj +++ b/test/Squirrel.Tests.csproj @@ -1,7 +1,7 @@  - + Debug @@ -13,7 +13,7 @@ Squirrel.Tests v4.5 512 - 49fa4dd1 + 76e21819 true @@ -42,6 +42,15 @@ MinimumRecommendedRules.ruleset + + ..\packages\DeltaCompressionDotNet.1.0.0\lib\net45\DeltaCompressionDotNet.dll + + + ..\packages\DeltaCompressionDotNet.1.0.0\lib\net45\DeltaCompressionDotNet.MsDelta.dll + + + ..\packages\DeltaCompressionDotNet.1.0.0\lib\net45\DeltaCompressionDotNet.PatchApi.dll + ..\ext\ICSharpCode.SharpZipLib.dll @@ -85,7 +94,6 @@ - @@ -122,8 +130,8 @@ This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + "$(SolutionDir).nuget\NuGet.exe" pack "$(ProjectPath)" -OutputDirectory "$(ProjectDir)$(OutDir)." -Properties Configuration=$(Configuration) diff --git a/test/SquirrelAwareExecutableDetectorTests.cs b/test/SquirrelAwareExecutableDetectorTests.cs index a67698442..43dea336e 100644 --- a/test/SquirrelAwareExecutableDetectorTests.cs +++ b/test/SquirrelAwareExecutableDetectorTests.cs @@ -39,9 +39,7 @@ public void SquirrelAwareViaVersionBlock() [Fact] public void SquirrelAwareViaAssemblyAttribute() { - var target = Path.Combine( - IntegrationTestHelper.GetIntegrationTestRootDirectory(), - "..", "src", "Update", "bin", "Release", "Update.exe"); + var target = Assembly.GetExecutingAssembly().Location; Assert.True(File.Exists(target)); @@ -52,7 +50,7 @@ public void SquirrelAwareViaAssemblyAttribute() [Fact] public void NotSquirrelAware() { - var target = Assembly.GetExecutingAssembly().Location; + var target = IntegrationTestHelper.GetPath("..", "src", "Update", "bin", "Release", "Update.exe"); Assert.True(File.Exists(target)); var ret = SquirrelAwareExecutableDetector.GetPESquirrelAwareVersion(target); diff --git a/test/fixtures/Setup.exe b/test/fixtures/Setup.exe new file mode 100644 index 000000000..ec119f1de Binary files /dev/null and b/test/fixtures/Setup.exe differ diff --git a/test/fixtures/Squirrel.Core.1.0.0.0-full.nupkg b/test/fixtures/Squirrel.Core.1.0.0.0-full.nupkg index e6d00579e..f78c99628 100644 Binary files a/test/fixtures/Squirrel.Core.1.0.0.0-full.nupkg and b/test/fixtures/Squirrel.Core.1.0.0.0-full.nupkg differ diff --git a/test/fixtures/Squirrel.Core.1.1.0.0-delta.nupkg b/test/fixtures/Squirrel.Core.1.1.0.0-delta.nupkg index 6ac6d8118..9c294cfd6 100644 Binary files a/test/fixtures/Squirrel.Core.1.1.0.0-delta.nupkg and b/test/fixtures/Squirrel.Core.1.1.0.0-delta.nupkg differ diff --git a/test/fixtures/Squirrel.Core.1.1.0.0-full.nupkg b/test/fixtures/Squirrel.Core.1.1.0.0-full.nupkg index e88c04877..a85b10ae6 100644 Binary files a/test/fixtures/Squirrel.Core.1.1.0.0-full.nupkg and b/test/fixtures/Squirrel.Core.1.1.0.0-full.nupkg differ diff --git a/test/fixtures/bsdiff/atom-137.0.exe b/test/fixtures/bsdiff/atom-137.0.exe deleted file mode 100644 index cfff6d3fd..000000000 Binary files a/test/fixtures/bsdiff/atom-137.0.exe and /dev/null differ diff --git a/test/fixtures/bsdiff/atom-137.1.exe b/test/fixtures/bsdiff/atom-137.1.exe deleted file mode 100644 index ea250a762..000000000 Binary files a/test/fixtures/bsdiff/atom-137.1.exe and /dev/null differ diff --git a/test/packages.config b/test/packages.config index 7866536bb..5f36ad0ff 100644 --- a/test/packages.config +++ b/test/packages.config @@ -1,5 +1,6 @@  +