src/managed/DiffGen/ArchiveUtility/ItemDefinition.cs (259 lines of code) (raw):

/** * @file ItemDefinition.cs * * @copyright Copyright (c) Microsoft Corporation. * Licensed under the MIT License. */ namespace ArchiveUtility { using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Security.Authentication; using System.Threading; [SuppressMessage("Microsoft.StyleCop.CSharp.ReadabilityRules", "SA1121", Justification = "We want to be explicit about bit-width using these aliases.")] public class ItemDefinition : IEquatable<ItemDefinition>, IComparable<ItemDefinition> { public Dictionary<HashAlgorithmType, Hash> Hashes { get; set; } public List<string> Names { get; set; } public UInt64 Length { get; set; } public ItemDefinition() { } public ItemDefinition(UInt64 length) { Length = length; } public ItemDefinition(UInt64 length, Dictionary<HashAlgorithmType, Hash> hashes, List<string> names) { Length = length; Hashes = hashes; Names = names; } public ItemDefinition WithHash(Hash hash) { Dictionary<HashAlgorithmType, Hash> hashes = new(); List<string> names = new(); foreach (var typeAndHash in Hashes) { hashes.Add(typeAndHash.Key, typeAndHash.Value); } hashes.Add(hash.Type, hash); var newItem = new ItemDefinition(Length, hashes, names); return newItem; } public Hash GetSha256Hash() { if (!Hashes.ContainsKey(HashAlgorithmType.Sha256)) { return null; } return Hashes[HashAlgorithmType.Sha256]; } public string GetSha256HashString() { if (!Hashes.ContainsKey(HashAlgorithmType.Sha256)) { return null; } return Hashes[HashAlgorithmType.Sha256].ValueString(); } public ItemDefinition WithName(string name) { Dictionary<HashAlgorithmType, Hash> hashes = new(); List<string> names = new(); foreach (var typeAndHash in Hashes) { hashes.Add(typeAndHash.Key, typeAndHash.Value); } names.AddRange(Names); names.Add(name); var newItem = new ItemDefinition(Length, hashes, names); return newItem; } public bool IsGapChunk() { return Names.Any(x => x.StartsWith(ChunkNames.GapChunkNamePrefix)); } public override string ToString() { string value = "{len: " + Length.ToString() + ", Hashes: {"; foreach (var hash in Hashes) { value += hash.Value.ValueString() + ", "; } value += "}"; if (Names.Count > 0) { value += ", Names: { "; foreach (var name in Names) { value += "\"" + name + "\","; } value += "}"; } value += "}"; return value; } public int CompareTo(ItemDefinition other) { return Compare(this, other); } public static int Compare(ItemDefinition x, ItemDefinition y) { if (x.Length != y.Length) { return (x.Length < y.Length) ? -1 : 1; } if (x.Hashes.Count != y.Hashes.Count) { return x.Hashes.Count - y.Hashes.Count; } if ((x.Hashes.Count == 0) && (x.Names.Count != y.Names.Count)) { return x.Names.Count - y.Names.Count; } var sortedXHashes = x.Hashes.OrderBy(x => x.Key); var sortedYHashes = y.Hashes.OrderBy(y => y.Key); foreach (var hashesPair in sortedXHashes.Zip(sortedYHashes)) { if (hashesPair.First.Key != hashesPair.Second.Key) { return hashesPair.First.Key.CompareTo(hashesPair.Second.Key); } byte[] hashXValue = hashesPair.First.Value.Value; byte[] hashYValue = hashesPair.Second.Value.Value; foreach (var hashBytePair in hashXValue.Zip(hashYValue)) { if (hashBytePair.First != hashBytePair.Second) { return hashBytePair.First - hashBytePair.Second; } } } if (x.Hashes.Count == 0) { var sortedXNames = x.Names.OrderBy(x => x); var sortedYNames = y.Names.OrderBy(x => x); foreach (var namePair in sortedXNames.Zip(sortedYNames)) { var nameCompareResult = namePair.First.CompareTo(namePair.Second); if (nameCompareResult != 0) { return nameCompareResult; } } } return 0; } public override int GetHashCode() { int result = Length.GetHashCode(); foreach (var entry in Hashes.OrderBy(x => x.Key)) { result = HashCode.Combine(result, entry.Key.GetHashCode(), entry.Value.GetHashCode()); } if (Hashes.Count == 0) { foreach (var name in Names) { result = HashCode.Combine(result, name.GetHashCode()); } } return result; } public static bool Equals(ItemDefinition x, ItemDefinition y) { if (x.Length != y.Length) { return false; } if (x.Hashes.Count != y.Hashes.Count) { return false; } if ((x.Hashes.Count == 0) && (x.Names.Count != y.Names.Count)) { return false; } foreach (var entry in x.Hashes) { var hashType = entry.Key; if (!y.Hashes.ContainsKey(hashType)) { return false; } var xHash = entry.Value; var yHash = y.Hashes[hashType]; if (!Enumerable.SequenceEqual(xHash.Value, yHash.Value)) { return false; } } if (x.Hashes.Count == 0) { return Enumerable.SequenceEqual(x.Names, y.Names); } return true; } public bool Equals(ItemDefinition other) { return Equals(this, other); } public override bool Equals(object obj) { if (obj is ItemDefinition) { return Equals(this, (ItemDefinition)obj); } return base.Equals(obj); } public static ItemDefinition FromBinaryReader(BinaryReader reader, UInt64 length) { var hashes = BinaryData.CalculateHashes(reader, length); List<string> names = new(); var newItem = new ItemDefinition(length, hashes, names); return newItem; } public static ItemDefinition FromByteSpan(ReadOnlySpan<byte> data) { var hashes = BinaryData.CalculateHashes(data); return new ItemDefinition((UInt64)data.Length, hashes, new List<string>()); } private static int GetMaxRetryCount(string path) { const long maxRetryBase = 10; long fileSize = new FileInfo(path).Length; long tenMegabytes = 10 * 1024 * 1024; return (int)Math.Min(20L, maxRetryBase + (fileSize / tenMegabytes)); } public static ItemDefinition FromFile(string path) { FileInfo deltaFileInfo = new(path); if (deltaFileInfo.Length < 0) { throw new Exception($"File length was negative for file: {path}"); } int retries = 0; Exception lastException = null; int maxRetries = GetMaxRetryCount(path); int sleepTimeMilliseconds = 1000; while (retries++ < maxRetries) { try { using var stream = File.Open(path, FileMode.Open, FileAccess.Read); using var reader = new BinaryReader(stream); return FromBinaryReader(reader, (ulong)deltaFileInfo.Length); } catch (IOException e) { lastException = e; Thread.Sleep(sleepTimeMilliseconds); sleepTimeMilliseconds += 1000; } } throw new Exception($"Failed to get item from file: {path}. {lastException.Message}", lastException); } public string GetExtractionPath(string root) { return Path.Combine(root, GetSha256HashString()); } } }