src/managed/DiffGen/DiffGeneration/Workers/SelectItemsForDelta.cs (154 lines of code) (raw):
/**
* @file SelectItemsForDelta.cs
*
* @copyright Copyright (c) Microsoft Corporation.
* Licensed under the MIT License.
*/
namespace Microsoft.Azure.DeviceUpdate.Diffs.Workers;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using ArchiveUtility;
using Microsoft.Azure.DeviceUpdate.Diffs.Utility;
using Microsoft.Extensions.Logging;
public class SelectItemsForDelta : Worker
{
public string SourceFile { get; set; }
public string TargetFile { get; set; }
public ArchiveTokenization SourceTokens { get; set; }
public ArchiveTokenization TargetTokens { get; set; }
public Diff Diff { get; set; }
public HashSet<ItemDefinition> SourceItemsNeeded { get; set; } = new();
public HashSet<ItemDefinition> TargetItemsNeeded { get; set; } = new();
public DeltaPlans DeltaPlans { get; set; } = new();
public SelectItemsForDelta(ILogger logger, string workingFolder, CancellationToken cancellationToken)
: base(logger, workingFolder, cancellationToken)
{
}
protected override void ExecuteInternal()
{
AddPayloadDeltas();
var neededItems = Diff.GetNeededItems().ToHashSet();
AddSizeBasedDeltas(neededItems);
var deltaPlanJsonPath = Path.Combine(WorkingFolder, "DeltaPlans.json");
Logger.LogInformation("Writing Delta Plan to: {deltaPlanJsonPath}", deltaPlanJsonPath);
using (var stream = File.Create(deltaPlanJsonPath))
{
DeltaPlans.WriteJson(stream, true);
}
Logger.LogInformation("DeltaPlans contains {DeltaPlanCount} entries.", DeltaPlans.Entries.Count);
}
private void AddPayloadDeltas()
{
if (TargetTokens.Payload == null || !TargetTokens.Payload.Any() || SourceTokens.Payload == null || !SourceTokens.Payload.Any())
{
Logger.LogError("No payloads to process.");
return;
}
List<string> newItems = new();
List<string> identicalItems = new();
List<string> differentItems = new();
ulong totalNew = 0;
ulong totalIdentical = 0;
ulong totalDifferent = 0;
foreach (var payloadEntry in TargetTokens.Payload)
{
var targetPayloadFullPath = payloadEntry.Key.Name;
var targetPayloadItems = payloadEntry.Value;
// This will also match exact matches
var sourceWildcardMatches = SourceTokens.GetPayloadMatchingWildcard(targetPayloadFullPath);
if (sourceWildcardMatches.Count() == 0)
{
newItems.Add($"{payloadEntry.Key.Name}, {string.Join(",", payloadEntry.Value.Select(x => x.ToString()))}");
ulong totalItemBytes = 0;
foreach (var item in targetPayloadItems)
{
totalItemBytes += item.Length;
}
totalNew += totalItemBytes;
continue;
}
foreach (var targetItem in targetPayloadItems)
{
foreach (var sourceItem in sourceWildcardMatches)
{
if (targetItem.Equals(sourceItem))
{
identicalItems.Add($"{payloadEntry.Key.Name},{string.Join(",", payloadEntry.Value.Select(x => x.ToString()))}");
totalIdentical += targetItem.Length;
continue;
}
// If this item is an archive then it will be assembled from
// its recipes those items should be diffed, not the archive itself
if (TargetTokens.HasArchiveItem(targetItem))
{
break;
}
differentItems.Add($"{payloadEntry.Key.Name}, {string.Join(",", payloadEntry.Value.Select(x => x.ToString()))}");
totalDifferent += targetItem.Length;
var plan = new DeltaPlan(sourceItem, targetItem, targetPayloadFullPath, true);
DeltaPlans.AddDeltaPlan(targetItem, plan);
TargetItemsNeeded.Add(targetItem);
SourceItemsNeeded.Add(sourceItem);
}
}
}
var newItemsFile = Path.Combine(WorkingFolder, "NewItems.txt");
File.WriteAllLines(newItemsFile, newItems);
var identicalItemsFile = Path.Combine(WorkingFolder, "IdenticalItems.txt");
File.WriteAllLines(identicalItemsFile, identicalItems);
var differentItemsFile = Path.Combine(WorkingFolder, "DifferentItems.txt");
File.WriteAllLines(differentItemsFile, differentItems);
Logger.LogInformation("Total new items found with no basis: Count: {newItemsCount:N0}, Bytes: {totalNew:N0}. Details written to {newItemsFile}", newItems.Count(), totalNew, newItemsFile);
Logger.LogInformation("Total identical items found: Count: {identicalItemsCount:N0}, Bytes: {totalIdentical:N0}. Details written to {identicalItemsFile}", identicalItems.Count(), totalIdentical, identicalItemsFile);
Logger.LogInformation("Total different items found: Count: {differentItemsCount:N0}, Bytes: {totalDifferent:N0}. Details written to {differentItemsFile}", differentItems.Count(), totalDifferent, differentItemsFile);
}
private void AddSizeBasedDeltas(HashSet<ItemDefinition> neededItems)
{
Dictionary<ulong, HashSet<ItemDefinition>> sourceItemsBySize = new();
// build index of items with recipes by size from source
foreach (var recipe in SourceTokens.Recipes)
{
var result = recipe.Result;
if (!sourceItemsBySize.ContainsKey(result.Length))
{
sourceItemsBySize.Add(result.Length, new());
}
sourceItemsBySize[result.Length].Add(result);
}
foreach (var needed in neededItems)
{
if (needed.Length < 256)
{
continue;
}
if (DeltaPlans.HasPlan(needed))
{
continue;
}
if (sourceItemsBySize.ContainsKey(needed.Length))
{
bool matched = false;
foreach (var sourceItem in sourceItemsBySize[needed.Length])
{
if (AnyMatchingNames(needed, sourceItem))
{
matched = true;
var plan = new DeltaPlan(sourceItem, needed, string.Empty, false);
DeltaPlans.AddDeltaPlan(needed, plan);
TargetItemsNeeded.Add(needed);
SourceItemsNeeded.Add(sourceItem);
break;
}
}
if (matched)
{
continue;
}
foreach (var sourceItem in sourceItemsBySize[needed.Length])
{
var plan = new DeltaPlan(sourceItem, needed, string.Empty, false);
DeltaPlans.AddDeltaPlan(needed, plan);
TargetItemsNeeded.Add(needed);
SourceItemsNeeded.Add(sourceItem);
}
}
}
}
private bool AnyMatchingNames(ItemDefinition left, ItemDefinition right)
{
return left.Names.Any(x => right.Names.Contains(x));
}
}