From 59b1235ac8de466165585c80a5f59571bd296517 Mon Sep 17 00:00:00 2001 From: Rebecca Wallander Date: Sat, 4 Apr 2026 12:28:21 +0200 Subject: [PATCH] Add ability to merge flux --- Aaru.Core/Image/Merge/Calculator.cs | 54 +++++----- Aaru.Core/Image/Merge/Flux.cs | 148 ++++++++++++++++++++++------ Aaru.Core/Image/Merge/Merger.cs | 20 ++-- Aaru.Localization/UI.Designer.cs | 18 ++++ Aaru.Localization/UI.es.resx | 7 +- Aaru.Localization/UI.resx | 7 +- 6 files changed, 182 insertions(+), 72 deletions(-) diff --git a/Aaru.Core/Image/Merge/Calculator.cs b/Aaru.Core/Image/Merge/Calculator.cs index 4ebca9b0e..e084f6908 100644 --- a/Aaru.Core/Image/Merge/Calculator.cs +++ b/Aaru.Core/Image/Merge/Calculator.cs @@ -9,6 +9,24 @@ namespace Aaru.Core.Image; public sealed partial class Merger { + /// + /// Single extent covering sectors 0 .. sectorCount - 1, or an empty list when + /// is zero (avoids 0 - 1 unsigned underflow in callers). + /// + static List CreateDefaultDumpExtents(ulong sectorCount) + { + if(sectorCount == 0) return new List(); + + return new List + { + new Extent + { + Start = 0, + End = sectorCount - 1 + } + }; + } + List CalculateSectorsToCopy(IMediaImage primaryImage, IMediaImage secondaryImage, Resume primaryResume, Resume secondaryResume, List overrideSectorsList) { @@ -16,14 +34,7 @@ public sealed partial class Merger [ new DumpHardware { - Extents = - [ - new Extent - { - Start = 0, - End = primaryImage.Info.Sectors - 1 - } - ] + Extents = CreateDefaultDumpExtents(primaryImage.Info.Sectors) } ]; @@ -32,14 +43,7 @@ public sealed partial class Merger [ new DumpHardware { - Extents = - [ - new Extent - { - Start = 0, - End = secondaryImage.Info.Sectors - 1 - } - ] + Extents = CreateDefaultDumpExtents(secondaryImage.Info.Sectors) } ]; @@ -93,14 +97,7 @@ public sealed partial class Merger [ new DumpHardware { - Extents = - [ - new Extent - { - Start = 0, - End = primaryImage.Info.Sectors - 1 - } - ] + Extents = CreateDefaultDumpExtents(primaryImage.Info.Sectors) } ]; @@ -109,14 +106,7 @@ public sealed partial class Merger [ new DumpHardware { - Extents = - [ - new Extent - { - Start = 0, - End = secondaryImage.Info.Sectors - 1 - } - ] + Extents = CreateDefaultDumpExtents(secondaryImage.Info.Sectors) } ]; diff --git a/Aaru.Core/Image/Merge/Flux.cs b/Aaru.Core/Image/Merge/Flux.cs index d3fbd189d..d5efc9a4d 100644 --- a/Aaru.Core/Image/Merge/Flux.cs +++ b/Aaru.Core/Image/Merge/Flux.cs @@ -1,52 +1,140 @@ +using System.Collections.Generic; +using System.Linq; using Aaru.CommonTypes.Enums; using Aaru.CommonTypes.Interfaces; +using Aaru.CommonTypes.Structs; namespace Aaru.Core.Image; public sealed partial class Merger { - // TODO: Should we return error any time? - // TODO: Add progress reporting - ErrorNumber CopyFlux(IFluxImage inputFlux, IWritableFluxImage outputFlux) + ErrorNumber MergeFlux(IFluxImage primaryFlux, IFluxImage secondaryFlux, IWritableFluxImage outputFlux) { - for(ushort track = 0; track < inputFlux.Info.Cylinders; track++) + ErrorNumber error = primaryFlux.GetAllFluxCaptures(out List primaryCaptures); + + if(error != ErrorNumber.NoError) return error; + + if(primaryCaptures is null) primaryCaptures = new List(); + + List secondaryCaptures = new List(); + + if(secondaryFlux != null) { - for(uint head = 0; head < inputFlux.Info.Heads; head++) + error = secondaryFlux.GetAllFluxCaptures(out secondaryCaptures); + + if(error != ErrorNumber.NoError) return error; + + if(secondaryCaptures is null) secondaryCaptures = new List(); + } + + Dictionary<(uint Head, ushort Track, byte SubTrack), List> primaryByLocation = + GroupFluxCapturesByLocation(primaryCaptures); + + Dictionary<(uint Head, ushort Track, byte SubTrack), List> secondaryByLocation = + GroupFluxCapturesByLocation(secondaryCaptures); + + HashSet<(uint Head, ushort Track, byte SubTrack)> allKeys = new HashSet<(uint Head, ushort Track, byte SubTrack)>(); + + foreach((uint Head, ushort Track, byte SubTrack) key in primaryByLocation.Keys) allKeys.Add(key); + + foreach((uint Head, ushort Track, byte SubTrack) key in secondaryByLocation.Keys) allKeys.Add(key); + + List<(uint Head, ushort Track, byte SubTrack)> sortedKeys = + allKeys.OrderBy(static k => k.Track).ThenBy(static k => k.Head).ThenBy(static k => k.SubTrack).ToList(); + + foreach((uint Head, ushort Track, byte SubTrack) key in sortedKeys) + { + List primaryGroup = + primaryByLocation.TryGetValue(key, out List pg) ? pg : new List(); + + List secondaryGroup = + secondaryByLocation.TryGetValue(key, out List sg) ? sg : new List(); + + primaryGroup.Sort((FluxCapture a, FluxCapture b) => a.CaptureIndex.CompareTo(b.CaptureIndex)); + secondaryGroup.Sort((FluxCapture a, FluxCapture b) => a.CaptureIndex.CompareTo(b.CaptureIndex)); + + uint outputIndex = 0; + + foreach(FluxCapture capture in primaryGroup) { - ErrorNumber error = inputFlux.SubTrackLength(head, track, out byte subTrackLen); + error = primaryFlux.ReadFluxCapture(capture.Head, + capture.Track, + capture.SubTrack, + capture.CaptureIndex, + out ulong indexResolution, + out ulong dataResolution, + out byte[] indexBuffer, + out byte[] dataBuffer); - if(error != ErrorNumber.NoError) continue; + if(error != ErrorNumber.NoError) return error; - for(byte subTrackIndex = 0; subTrackIndex < subTrackLen; subTrackIndex++) + error = outputFlux.WriteFluxCapture(indexResolution, + dataResolution, + indexBuffer, + dataBuffer, + capture.Head, + capture.Track, + capture.SubTrack, + outputIndex); + + if(error != ErrorNumber.NoError) return error; + + outputIndex++; + } + + if(secondaryFlux != null) + { + foreach(FluxCapture capture in secondaryGroup) { - error = inputFlux.CapturesLength(head, track, subTrackIndex, out uint capturesLen); + error = secondaryFlux.ReadFluxCapture(capture.Head, + capture.Track, + capture.SubTrack, + capture.CaptureIndex, + out ulong indexResolution, + out ulong dataResolution, + out byte[] indexBuffer, + out byte[] dataBuffer); - if(error != ErrorNumber.NoError) continue; + if(error != ErrorNumber.NoError) return error; - for(uint captureIndex = 0; captureIndex < capturesLen; captureIndex++) - { - inputFlux.ReadFluxCapture(head, - track, - subTrackIndex, - captureIndex, - out ulong indexResolution, - out ulong dataResolution, - out byte[] indexBuffer, - out byte[] dataBuffer); + error = outputFlux.WriteFluxCapture(indexResolution, + dataResolution, + indexBuffer, + dataBuffer, + capture.Head, + capture.Track, + capture.SubTrack, + outputIndex); - outputFlux.WriteFluxCapture(indexResolution, - dataResolution, - indexBuffer, - dataBuffer, - head, - track, - subTrackIndex, - captureIndex); - } + if(error != ErrorNumber.NoError) return error; + + outputIndex++; } } } return ErrorNumber.NoError; } -} \ No newline at end of file + + static Dictionary<(uint Head, ushort Track, byte SubTrack), List> GroupFluxCapturesByLocation( + List captures) + { + Dictionary<(uint Head, ushort Track, byte SubTrack), List> result = + new Dictionary<(uint Head, ushort Track, byte SubTrack), List>(); + + foreach(FluxCapture capture in captures) + { + (uint Head, ushort Track, byte SubTrack) key = (capture.Head, capture.Track, capture.SubTrack); + + if(!result.TryGetValue(key, out List list)) + { + list = new List(); + result[key] = list; + } + + list.Add(capture); + } + + return result; + } +} diff --git a/Aaru.Core/Image/Merge/Merger.cs b/Aaru.Core/Image/Merge/Merger.cs index 947818d15..de7693a30 100644 --- a/Aaru.Core/Image/Merge/Merger.cs +++ b/Aaru.Core/Image/Merge/Merger.cs @@ -220,12 +220,15 @@ public sealed partial class Merger InitProgress?.Invoke(); PulseProgress?.Invoke(UI.Calculating_sectors_to_merge); - List sectorsToCopyFromSecondImage = - CalculateSectorsToCopy(primaryImage, secondaryImage, primaryResume, secondaryResume, overrideSectorsList); + List sectorsToCopyFromSecondImage = + CalculateSectorsToCopy(primaryImage, secondaryImage, primaryResume, secondaryResume, overrideSectorsList); EndProgress?.Invoke(); - if(sectorsToCopyFromSecondImage.Count == 0) + // Flux images might contain no decoded data, which results in a sector count of 0. We allow this if the image contains flux. + var containsFlux = primaryImage is IFluxImage || secondaryImage is IFluxImage; + + if(sectorsToCopyFromSecondImage.Count == 0 && !containsFlux) { StoppingErrorMessage ?.Invoke(UI.No_sectors_to_merge__output_image_will_be_identical_to_primary_image_not_continuing); @@ -344,10 +347,15 @@ public sealed partial class Merger if(errno != ErrorNumber.NoError) return errno; - if(primaryImage is IFluxImage inputFlux && outputFormat is IWritableFluxImage outputFlux) + if(primaryImage is IFluxImage primaryFlux && outputFormat is IWritableFluxImage outputFlux) { - UpdateStatus?.Invoke(UI.Flux_data_will_be_copied_as_is_from_primary_image); - errno = CopyFlux(inputFlux, outputFlux); + IFluxImage secondaryFlux = secondaryImage as IFluxImage; + + UpdateStatus?.Invoke(secondaryFlux != null + ? UI.Flux_merge_primary_then_secondary_appended + : UI.Flux_merge_primary_flux_only_secondary_not_flux_image); + + errno = MergeFlux(primaryFlux, secondaryFlux, outputFlux); if(errno != ErrorNumber.NoError) return errno; } diff --git a/Aaru.Localization/UI.Designer.cs b/Aaru.Localization/UI.Designer.cs index 05a940b97..a56190ee1 100644 --- a/Aaru.Localization/UI.Designer.cs +++ b/Aaru.Localization/UI.Designer.cs @@ -5115,6 +5115,24 @@ namespace Aaru.Localization { return ResourceManager.GetString("Flux_data_will_be_copied_as_is_from_primary_image", resourceCulture); } } + + /// + /// Looks up localized string similar to Merging flux: primary captures first, then secondary captures appended with renumbered indices... + /// + public static string Flux_merge_primary_then_secondary_appended { + get { + return ResourceManager.GetString("Flux_merge_primary_then_secondary_appended", resourceCulture); + } + } + + /// + /// Looks up localized string similar to Secondary image is not a flux image; copying flux captures from the primary image only... + /// + public static string Flux_merge_primary_flux_only_secondary_not_flux_image { + get { + return ResourceManager.GetString("Flux_merge_primary_flux_only_secondary_not_flux_image", resourceCulture); + } + } /// /// Looks up a localized string similar to Force geometry, only supported in not tape block media. Specify as C/H/S.. diff --git a/Aaru.Localization/UI.es.resx b/Aaru.Localization/UI.es.resx index c2b869968..49a56a7b9 100644 --- a/Aaru.Localization/UI.es.resx +++ b/Aaru.Localization/UI.es.resx @@ -5452,8 +5452,11 @@ Probadores: [slateblue1]Se copiarán [teal]{0}[/] sectores de la imagen secundaria.[/] - - [slateblue1]Los datos de flujo se copiarán tal cual de la imagen primaria...[/] + + [slateblue1]Combinando flujo: primero capturas de la imagen primaria, luego las de la secundaria con índices renumerados...[/] + + + [slateblue1]La imagen secundaria no es de flujo; copiando solo las capturas de flujo de la imagen primaria...[/] [slateblue1]Copiando fichero [lime]{0}[/] de partición [teal]{1}[/]...[/] diff --git a/Aaru.Localization/UI.resx b/Aaru.Localization/UI.resx index 962f662de..b0c60dd0f 100644 --- a/Aaru.Localization/UI.resx +++ b/Aaru.Localization/UI.resx @@ -5536,8 +5536,11 @@ Do you want to continue? [slateblue1]Will copy [teal]{0}[/] sectors from secondary image.[/] - - [slateblue1]Flux data will be copied as-is from primary image...[/] + + [slateblue1]Merging flux: primary captures first, then secondary captures appended with renumbered indices...[/] + + + [slateblue1]Secondary image is not a flux image; copying flux captures from the primary image only...[/] [slateblue1]Copying file [lime]{0}[/] of partition [teal]{1}[/]...[/]