public async Task BuildDeltaAsync(Stream newFileStream, ISignatureReader signatureReader, IDeltaWriter deltaWriter) { var signature = signatureReader.ReadSignature(); var chunks = signature.Chunks; var newFileVerificationHashAlgorithm = SupportedAlgorithms.Hashing.Md5(); var newFileHash = await newFileVerificationHashAlgorithm.ComputeHashAsync(newFileStream).ConfigureAwait(false); newFileStream.Seek(0, SeekOrigin.Begin); deltaWriter.WriteMetadata(new DeltaMetadata { HashAlgorithm = signature.HashAlgorithm.Name, ExpectedFileHashAlgorithm = newFileVerificationHashAlgorithm.Name, ExpectedFileHash = Convert.ToBase64String(newFileHash) }); chunks = OrderChunksByChecksum(chunks); var chunkMap = CreateChunkMap(chunks, out int maxChunkSize, out int minChunkSize); var buffer = new byte[readBufferSize]; long lastMatchPosition = 0; var fileSize = newFileStream.Length; ProgressReport?.Report(new ProgressReport { Operation = ProgressOperationType.BuildingDelta, CurrentPosition = 0, Total = fileSize }); while (true) { var startPosition = newFileStream.Position; var read = await newFileStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); if (read < 0) { break; } var checksumAlgorithm = signature.RollingChecksumAlgorithm; uint checksum = 0; var remainingPossibleChunkSize = maxChunkSize; for (var i = 0; i < read - minChunkSize + 1; i++) { var readSoFar = startPosition + i; var remainingBytes = read - i; if (remainingBytes < maxChunkSize) { remainingPossibleChunkSize = minChunkSize; } if (i == 0 || remainingBytes < maxChunkSize) { checksum = checksumAlgorithm.Calculate(buffer, i, remainingPossibleChunkSize); } else { var remove = buffer[i - 1]; var add = buffer[i + remainingPossibleChunkSize - 1]; checksum = checksumAlgorithm.Rotate(checksum, remove, add, remainingPossibleChunkSize); } ProgressReport?.Report(new ProgressReport { Operation = ProgressOperationType.BuildingDelta, CurrentPosition = readSoFar, Total = fileSize }); if (readSoFar - (lastMatchPosition - remainingPossibleChunkSize) < remainingPossibleChunkSize) { continue; } if (!chunkMap.ContainsKey(checksum)) { continue; } var startIndex = chunkMap[checksum]; for (var j = startIndex; j < chunks.Count && chunks[j].RollingChecksum == checksum; j++) { var chunk = chunks[j]; var hash = signature.HashAlgorithm.ComputeHash(buffer, i, remainingPossibleChunkSize); if (StructuralComparisons.StructuralEqualityComparer.Equals(hash, chunks[j].Hash)) { readSoFar = readSoFar + remainingPossibleChunkSize; var missing = readSoFar - lastMatchPosition; if (missing > remainingPossibleChunkSize) { await deltaWriter.WriteDataCommandAsync(newFileStream, lastMatchPosition, missing - remainingPossibleChunkSize).ConfigureAwait(false); } deltaWriter.WriteCopyCommand(new DataRange(chunk.StartOffset, chunk.Length)); lastMatchPosition = readSoFar; break; } } } if (read < buffer.Length) { break; } newFileStream.Position = newFileStream.Position - maxChunkSize + 1; } if (newFileStream.Length != lastMatchPosition) { await deltaWriter.WriteDataCommandAsync(newFileStream, lastMatchPosition, newFileStream.Length - lastMatchPosition).ConfigureAwait(false); } deltaWriter.Finish(); }
public async Task WriteDataCommandAsync(Stream source, long offset, long length) { FlushCurrentCopyCommand(); await decorated.WriteDataCommandAsync(source, offset, length).ConfigureAwait(false); }