From 087df7535eea00a24016ee7f0107c483fdf106d9 Mon Sep 17 00:00:00 2001 From: Rob Greene Date: Sun, 13 Mar 2022 18:03:53 -0500 Subject: [PATCH 1/7] Adding DiskDiff and enhancing the basic disk comparison capabilities. --- .../io/github/applecommander/acx/Main.java | 2 + .../acx/command/CompareCommand.java | 87 ++++++++ .../applecommander/storage/DiskGeometry.java | 39 ++++ .../applecommander/storage/FormattedDisk.java | 5 + .../storage/compare/ComparisonResult.java | 52 +++++ .../storage/compare/DiskDiff.java | 189 ++++++++++++++++++ .../storage/os/cpm/CpmFormatDisk.java | 8 + .../storage/os/dos33/DosFormatDisk.java | 8 + .../os/gutenberg/GutenbergFormatDisk.java | 8 + .../storage/os/nakedos/NakedosFormatDisk.java | 8 + .../storage/os/pascal/PascalFormatDisk.java | 8 + .../storage/os/prodos/ProdosFormatDisk.java | 8 + .../storage/os/rdos/RdosFormatDisk.java | 8 + .../applecommander/util/AppleUtil.java | 44 ---- .../applecommander/ui/UiBundle.properties | 8 +- .../applecommander/util/AppleUtilTest.java | 10 +- .../comparedisks/CompareDisksResultsPane.java | 51 ++--- 17 files changed, 461 insertions(+), 82 deletions(-) create mode 100644 app/cli-acx/src/main/java/io/github/applecommander/acx/command/CompareCommand.java create mode 100644 lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/DiskGeometry.java create mode 100644 lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/compare/ComparisonResult.java create mode 100644 lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/compare/DiskDiff.java diff --git a/app/cli-acx/src/main/java/io/github/applecommander/acx/Main.java b/app/cli-acx/src/main/java/io/github/applecommander/acx/Main.java index 8add951..fce9895 100644 --- a/app/cli-acx/src/main/java/io/github/applecommander/acx/Main.java +++ b/app/cli-acx/src/main/java/io/github/applecommander/acx/Main.java @@ -24,6 +24,7 @@ import java.util.logging.Level; import java.util.logging.LogManager; import java.util.logging.Logger; +import io.github.applecommander.acx.command.CompareCommand; import io.github.applecommander.acx.command.ConvertCommand; import io.github.applecommander.acx.command.CopyFileCommand; import io.github.applecommander.acx.command.CreateDiskCommand; @@ -53,6 +54,7 @@ import picocli.CommandLine.Option; optionListHeading = "%nOptions:%n", description = "'acx' experimental utility", subcommands = { + CompareCommand.class, ConvertCommand.class, CopyFileCommand.class, CreateDiskCommand.class, diff --git a/app/cli-acx/src/main/java/io/github/applecommander/acx/command/CompareCommand.java b/app/cli-acx/src/main/java/io/github/applecommander/acx/command/CompareCommand.java new file mode 100644 index 0000000..7791f50 --- /dev/null +++ b/app/cli-acx/src/main/java/io/github/applecommander/acx/command/CompareCommand.java @@ -0,0 +1,87 @@ +/* + * AppleCommander - An Apple ][ image utility. + * Copyright (C) 2019-2022 by Robert Greene and others + * robgreene at users.sourceforge.net + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package io.github.applecommander.acx.command; + +import java.util.function.Consumer; + +import com.webcodepro.applecommander.storage.Disk; +import com.webcodepro.applecommander.storage.compare.ComparisonResult; +import com.webcodepro.applecommander.storage.compare.DiskDiff; + +import io.github.applecommander.acx.base.ReadOnlyDiskImageCommandOptions; +import io.github.applecommander.acx.converter.DiskConverter; +import picocli.CommandLine.ArgGroup; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; +import picocli.CommandLine.Parameters; + +@Command(name = "compare", description = "Compare two disk images.") +public class CompareCommand extends ReadOnlyDiskImageCommandOptions { + @Parameters(arity = "1", converter = DiskConverter.class, description = "Second image to compare to.") + private Disk disk2; + + @ArgGroup(heading = "%nComparison Strategy Selection:%n") + private StrategySelection strategySelection = new StrategySelection(); + + @Override + public int handleCommand() throws Exception { + DiskDiff.Builder builder = DiskDiff.create(disk, disk2); + strategySelection.strategy.accept(builder); + ComparisonResult result = builder.compare(); + + if (result.getDifferenceCount() == 0) { + System.out.println("The disks match."); + } + else { + System.out.println("The disks do not match."); + result.getErrors().forEach(System.out::println); + result.getWarnings().forEach(System.out::println); + } + + return 0; + } + + public static class StrategySelection { + private Consumer strategy = this::nativeGeometry; + + @Option(names = "--native", description = "Compare by native geometry.") + private void selectNativeGeometry(boolean flag) { + strategy = this::nativeGeometry; + } + @Option(names = "--block", description = "Compare by block geometry.") + private void selectBlockGeometry(boolean flag) { + strategy = this::blockGeometry; + } + @Option(names = { "--track-sector", "--ts" }, description = "Compare by track/sector geometry.") + private void selectTrackSectorGeometry(boolean flag) { + strategy = this::trackSectorGeometry; + } + + private void nativeGeometry(DiskDiff.Builder builder) { + builder.selectCompareByNativeGeometry(); + } + private void blockGeometry(DiskDiff.Builder builder) { + builder.selectCompareByBlockGeometry(); + } + private void trackSectorGeometry(DiskDiff.Builder builder) { + builder.selectCompareByTrackSectorGeometry(); + } + } +} diff --git a/lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/DiskGeometry.java b/lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/DiskGeometry.java new file mode 100644 index 0000000..81bb424 --- /dev/null +++ b/lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/DiskGeometry.java @@ -0,0 +1,39 @@ +/* + * AppleCommander - An Apple ][ image utility. + * Copyright (C) 2021-2022 by Robert Greene and others + * robgreene at users.sourceforge.net + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package com.webcodepro.applecommander.storage; + +/** + * Indicates the broad disk geometry - track/sector or block. + * Note that BLOCK is meant to include only ProDOS/Pascal 512-byte + * blocks and not the RDOS 256 "blocks" (RDOS should remain under + * the track/sector geometry.) + */ +public enum DiskGeometry { + TRACK_SECTOR(256, "Track/Sector"), + BLOCK(512, "Block"); + + public int size; + public String text; + + private DiskGeometry(int size, String text) { + this.size = size; + this.text = text; + } +} diff --git a/lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/FormattedDisk.java b/lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/FormattedDisk.java index 52dd48e..4c72208 100644 --- a/lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/FormattedDisk.java +++ b/lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/FormattedDisk.java @@ -399,4 +399,9 @@ public abstract class FormattedDisk extends Disk implements DirectoryEntry { * Typically, the FileEntry.setFileData method should be used. */ public abstract void setFileData(FileEntry fileEntry, byte[] fileData) throws DiskFullException; + + /** + * Gives an indication on how this disk's geometry should be handled. + */ + public abstract DiskGeometry getDiskGeometry(); } diff --git a/lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/compare/ComparisonResult.java b/lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/compare/ComparisonResult.java new file mode 100644 index 0000000..0c831d8 --- /dev/null +++ b/lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/compare/ComparisonResult.java @@ -0,0 +1,52 @@ +/* + * AppleCommander - An Apple ][ image utility. + * Copyright (C) 2021-2022 by Robert Greene and others + * robgreene at users.sourceforge.net + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package com.webcodepro.applecommander.storage.compare; + +import java.util.ArrayList; +import java.util.List; + +public class ComparisonResult { + private List errors = new ArrayList<>(); + private List warnings = new ArrayList<>(); + + public boolean hasErrors() { + return !errors.isEmpty(); + } + public int getDifferenceCount() { + return errors.size() + warnings.size(); + } + + public void addError(Exception ex) { + errors.add(ex.getMessage()); + } + public void addError(String fmt, Object... args) { + errors.add(String.format(fmt, args)); + } + public void addWarning(String fmt, Object... args) { + warnings.add(String.format(fmt, args)); + } + + public List getErrors() { + return errors; + } + public List getWarnings() { + return warnings; + } +} diff --git a/lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/compare/DiskDiff.java b/lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/compare/DiskDiff.java new file mode 100644 index 0000000..e580a93 --- /dev/null +++ b/lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/compare/DiskDiff.java @@ -0,0 +1,189 @@ +/* + * AppleCommander - An Apple ][ image utility. + * Copyright (C) 2021-2022 by Robert Greene and others + * robgreene at users.sourceforge.net + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package com.webcodepro.applecommander.storage.compare; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.function.BiConsumer; + +import com.webcodepro.applecommander.storage.Disk; +import com.webcodepro.applecommander.storage.DiskGeometry; +import com.webcodepro.applecommander.storage.DiskUnrecognizedException; +import com.webcodepro.applecommander.storage.FormattedDisk; +import com.webcodepro.applecommander.storage.physical.ImageOrder; + +/** + * Perform a disk comparison based on selected strategy. + */ +public class DiskDiff { + public static ComparisonResult compare(Disk diskA, Disk diskB) { + return new DiskDiff(diskA, diskB).compare(); + } + public static Builder create(Disk diskA, Disk diskB) { + return new Builder(diskA, diskB); + } + + private Disk diskA; + private Disk diskB; + private ComparisonResult results = new ComparisonResult(); + + private BiConsumer diskComparisonStrategy = this::compareByNativeGeometry; + + private DiskDiff(Disk diskA, Disk diskB) { + Objects.requireNonNull(diskA); + Objects.requireNonNull(diskB); + this.diskA = diskA; + this.diskB = diskB; + } + + public ComparisonResult compare() { + FormattedDisk[] formattedDisksA = null; + try { + formattedDisksA = diskA.getFormattedDisks(); + } catch (DiskUnrecognizedException e) { + results.addError(e); + } + FormattedDisk[] formattedDisksB = null; + try { + formattedDisksB = diskB.getFormattedDisks(); + } catch (DiskUnrecognizedException e) { + results.addError(e); + } + + if (!results.hasErrors()) { + compareAll(formattedDisksA, formattedDisksB); + } + return results; + } + + public void compareAll(FormattedDisk[] formattedDisksA, FormattedDisk[] formattedDisksB) { + Objects.requireNonNull(formattedDisksA); + Objects.requireNonNull(formattedDisksB); + + if (formattedDisksA.length != formattedDisksB.length) { + results.addWarning("Cannot compare all disks; %s has %d while %s has %d.", + diskA.getFilename(), formattedDisksA.length, + diskB.getFilename(), formattedDisksB.length); + } + + int min = Math.min(formattedDisksA.length, formattedDisksB.length); + for (int i=0; i %d)", + orderA.getBlocksOnDevice(), orderB.getBlocksOnDevice()); + return; + } + + for (int block=0; block %d)", + orderA.getSectorsPerDisk(), orderB.getSectorsPerDisk()); + return; + } + + for (int track=0; track unequalSectors = new ArrayList<>(); + for (int sector=0; sector errorMessages = new ArrayList<>(); + Disk disk1 = null; try { - disk1 = new Disk(wizard.getDiskname1()).getFormattedDisks(); + disk1 = new Disk(wizard.getDiskname1()); } catch (Throwable t) { - errorMessages.append(textBundle. + errorMessages.add(textBundle. format("CompareDisksResultsPane.UnableToLoadDiskN", //$NON-NLS-1$ 1, t.getLocalizedMessage())); } - FormattedDisk[] disk2 = null; + Disk disk2 = null; try { - disk2 = new Disk(wizard.getDiskname2()).getFormattedDisks(); + disk2 = new Disk(wizard.getDiskname2()); } catch (Throwable t) { - errorMessages.append(textBundle. + errorMessages.add(textBundle. format("CompareDisksResultsPane.UnableToLoadDiskN", //$NON-NLS-1$ 2, t.getLocalizedMessage())); } if (disk1 != null && disk2 != null) { - if (disk1.length != disk2.length) { - errorMessages.append(textBundle.get( - "CompareDisksResultsPane.DifferentSizeError")); //$NON-NLS-1$ - } else { - boolean disk1TSformat = disk1[0].isCpmFormat() || disk1[0].isDosFormat() || disk1[0].isRdosFormat(); - boolean disk2TSformat = disk2[0].isCpmFormat() || disk2[0].isDosFormat() || disk2[0].isRdosFormat(); - if (disk1TSformat && disk2TSformat) { - if (!AppleUtil.disksEqualByTrackAndSector(disk1[0], disk2[0])) { - errorMessages.append(textBundle.get( - "CompareDisksResultsPane.DataDiffersMessage")); //$NON-NLS-1$ - } - } else if (!disk1TSformat && !disk2TSformat) { - if (!AppleUtil.disksEqualByBlock(disk1[0], disk2[0])) { - errorMessages.append(textBundle.get( - "CompareDisksResultsPane.DataDiffersMessage")); //$NON-NLS-1$ - } - } else { - errorMessages.append(textBundle.get( - "CompareDisksResultsPane.DifferentDataFormatError")); //$NON-NLS-1$ - } - } + ComparisonResult result = DiskDiff.compare(disk1, disk2); + errorMessages.addAll(result.getErrors()); + errorMessages.addAll(result.getWarnings()); } - if (errorMessages.length() == 0) { + if (errorMessages.size() == 0) { return textBundle.get("CompareDisksResultsPane.DisksMatch"); //$NON-NLS-1$ } - return errorMessages.toString(); + return String.join("\n", errorMessages); } } From 8ea6990dd5adc9712310ad323a99021c270a357d Mon Sep 17 00:00:00 2001 From: Rob Greene Date: Sun, 13 Mar 2022 20:50:31 -0500 Subject: [PATCH 2/7] Adding limit to comparison. --- .../acx/command/CompareCommand.java | 12 ++++++++-- .../storage/compare/ComparisonResult.java | 11 +++++++++ .../applecommander/ui/swt/wizard/Wizard.java | 1 + .../comparedisks/CompareDisksResultsPane.java | 3 +-- .../comparedisks/CompareDisksStartPane.java | 23 ++++++++++++++++--- .../comparedisks/CompareDisksWizard.java | 8 ++++++- 6 files changed, 50 insertions(+), 8 deletions(-) diff --git a/app/cli-acx/src/main/java/io/github/applecommander/acx/command/CompareCommand.java b/app/cli-acx/src/main/java/io/github/applecommander/acx/command/CompareCommand.java index 7791f50..b3d5cba 100644 --- a/app/cli-acx/src/main/java/io/github/applecommander/acx/command/CompareCommand.java +++ b/app/cli-acx/src/main/java/io/github/applecommander/acx/command/CompareCommand.java @@ -19,6 +19,7 @@ */ package io.github.applecommander.acx.command; +import java.util.Optional; import java.util.function.Consumer; import com.webcodepro.applecommander.storage.Disk; @@ -39,6 +40,9 @@ public class CompareCommand extends ReadOnlyDiskImageCommandOptions { @ArgGroup(heading = "%nComparison Strategy Selection:%n") private StrategySelection strategySelection = new StrategySelection(); + + @Option(names = { "-l", "--limit" }, description = "Set limit to messages displayed.") + private Optional limit = Optional.empty(); @Override public int handleCommand() throws Exception { @@ -51,8 +55,12 @@ public class CompareCommand extends ReadOnlyDiskImageCommandOptions { } else { System.out.println("The disks do not match."); - result.getErrors().forEach(System.out::println); - result.getWarnings().forEach(System.out::println); + limit.map(result::getLimitedMessages) + .orElseGet(result::getAllMessages) + .forEach(System.out::println); + if (result.getDifferenceCount() > limit.orElse(Integer.MAX_VALUE)) { + System.out.printf("There are %d more messages.\n", result.getDifferenceCount() - limit.get()); + } } return 0; diff --git a/lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/compare/ComparisonResult.java b/lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/compare/ComparisonResult.java index 0c831d8..5ba3d28 100644 --- a/lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/compare/ComparisonResult.java +++ b/lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/compare/ComparisonResult.java @@ -33,6 +33,17 @@ public class ComparisonResult { return errors.size() + warnings.size(); } + public List getAllMessages() { + List messages = new ArrayList<>(); + messages.addAll(errors); + messages.addAll(warnings); + return messages; + } + public List getLimitedMessages(int limit) { + List messages = getAllMessages(); + return messages.subList(0, Math.min(messages.size(), limit)); + } + public void addError(Exception ex) { errors.add(ex.getMessage()); } diff --git a/lib/ac-swt-common/src/main/java/com/webcodepro/applecommander/ui/swt/wizard/Wizard.java b/lib/ac-swt-common/src/main/java/com/webcodepro/applecommander/ui/swt/wizard/Wizard.java index 022d2ce..4465bdf 100644 --- a/lib/ac-swt-common/src/main/java/com/webcodepro/applecommander/ui/swt/wizard/Wizard.java +++ b/lib/ac-swt-common/src/main/java/com/webcodepro/applecommander/ui/swt/wizard/Wizard.java @@ -83,6 +83,7 @@ public abstract class Wizard { RowData rowData = new RowData(); rowData.width = logo.getImageData().width; rowData.height = logo.getImageData().height; + System.out.println(rowData.toString()); imageCanvas = new ImageCanvas(dialog, SWT.BORDER, logo, rowData); // Starting pane diff --git a/lib/ac-swt-common/src/main/java/com/webcodepro/applecommander/ui/swt/wizard/comparedisks/CompareDisksResultsPane.java b/lib/ac-swt-common/src/main/java/com/webcodepro/applecommander/ui/swt/wizard/comparedisks/CompareDisksResultsPane.java index 2d2176e..b42c3f7 100644 --- a/lib/ac-swt-common/src/main/java/com/webcodepro/applecommander/ui/swt/wizard/comparedisks/CompareDisksResultsPane.java +++ b/lib/ac-swt-common/src/main/java/com/webcodepro/applecommander/ui/swt/wizard/comparedisks/CompareDisksResultsPane.java @@ -117,8 +117,7 @@ public class CompareDisksResultsPane extends WizardPane { } if (disk1 != null && disk2 != null) { ComparisonResult result = DiskDiff.compare(disk1, disk2); - errorMessages.addAll(result.getErrors()); - errorMessages.addAll(result.getWarnings()); + errorMessages.addAll(result.getLimitedMessages(wizard.getMessageLimit())); } if (errorMessages.size() == 0) { return textBundle.get("CompareDisksResultsPane.DisksMatch"); //$NON-NLS-1$ diff --git a/lib/ac-swt-common/src/main/java/com/webcodepro/applecommander/ui/swt/wizard/comparedisks/CompareDisksStartPane.java b/lib/ac-swt-common/src/main/java/com/webcodepro/applecommander/ui/swt/wizard/comparedisks/CompareDisksStartPane.java index 8d8b863..fab4c27 100644 --- a/lib/ac-swt-common/src/main/java/com/webcodepro/applecommander/ui/swt/wizard/comparedisks/CompareDisksStartPane.java +++ b/lib/ac-swt-common/src/main/java/com/webcodepro/applecommander/ui/swt/wizard/comparedisks/CompareDisksStartPane.java @@ -24,7 +24,6 @@ import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.graphics.Color; import org.eclipse.swt.layout.RowData; import org.eclipse.swt.layout.RowLayout; import org.eclipse.swt.widgets.Button; @@ -50,6 +49,7 @@ public class CompareDisksStartPane extends WizardPane { private CompareDisksWizard wizard; private Text diskname1Text; private Text diskname2Text; + private Text limitText; /** * Constructor for CompareDisksStartPane. */ @@ -75,6 +75,7 @@ public class CompareDisksStartPane extends WizardPane { layout.marginTop = 5; layout.spacing = 3; control.setLayout(layout); + Label label = new Label(control, SWT.WRAP); label.setText(textBundle.get("CompareDisksStartPane.Description")); //$NON-NLS-1$ @@ -84,7 +85,6 @@ public class CompareDisksStartPane extends WizardPane { diskname1Text = new Text(control, SWT.WRAP | SWT.BORDER); if (wizard.getDiskname1() != null) diskname1Text.setText(wizard.getDiskname1()); diskname1Text.setLayoutData(new RowData(300, -1)); - diskname1Text.setBackground(new Color(control.getDisplay(), 255,255,255)); diskname1Text.setFocus(); diskname1Text.addModifyListener(new ModifyListener() { public void modifyText(ModifyEvent event) { @@ -113,7 +113,6 @@ public class CompareDisksStartPane extends WizardPane { diskname2Text = new Text(control, SWT.WRAP | SWT.BORDER); if (wizard.getDiskname2() != null) diskname2Text.setText(wizard.getDiskname2()); diskname2Text.setLayoutData(new RowData(300, -1)); - diskname2Text.setBackground(new Color(control.getDisplay(), 255,255,255)); diskname2Text.addModifyListener(new ModifyListener() { public void modifyText(ModifyEvent event) { Text text = (Text) event.getSource(); @@ -134,6 +133,16 @@ public class CompareDisksStartPane extends WizardPane { } } }); + + label = new Label(control, SWT.WRAP); + label.setText("Set limit on messages displayed:"); + + limitText = new Text(control, SWT.WRAP | SWT.BORDER); + limitText.setText(Integer.toString(wizard.getMessageLimit())); + limitText.setLayoutData(new RowData(200, -1)); + limitText.addModifyListener(this::limitTextModifyListener); + + parent.pack(); } /** * Get the next pane. A null return indicates the end of the wizard. @@ -166,4 +175,12 @@ public class CompareDisksStartPane extends WizardPane { return textBundle.format("CompareDisksStartPane.DiskNLabel", //$NON-NLS-1$ diskNumber); } + + protected void limitTextModifyListener(ModifyEvent event) { + try { + wizard.setMessageLimit(Integer.parseInt(limitText.getText())); + } catch (NumberFormatException e) { + limitText.setText(Integer.toString(wizard.getMessageLimit())); + } + } } diff --git a/lib/ac-swt-common/src/main/java/com/webcodepro/applecommander/ui/swt/wizard/comparedisks/CompareDisksWizard.java b/lib/ac-swt-common/src/main/java/com/webcodepro/applecommander/ui/swt/wizard/comparedisks/CompareDisksWizard.java index a06411d..da3acb6 100644 --- a/lib/ac-swt-common/src/main/java/com/webcodepro/applecommander/ui/swt/wizard/comparedisks/CompareDisksWizard.java +++ b/lib/ac-swt-common/src/main/java/com/webcodepro/applecommander/ui/swt/wizard/comparedisks/CompareDisksWizard.java @@ -34,6 +34,7 @@ import com.webcodepro.applecommander.ui.swt.wizard.WizardPane; public class CompareDisksWizard extends Wizard { private String diskname1; private String diskname2; + private int messageLimit = 10; /** * Constructor for ExportWizard. */ @@ -54,11 +55,16 @@ public class CompareDisksWizard extends Wizard { public String getDiskname2() { return diskname2; } + public int getMessageLimit() { + return messageLimit; + } public void setDiskname1(String string) { diskname1 = string; } public void setDiskname2(String string) { diskname2 = string; } - + public void setMessageLimit(int messageLimit) { + this.messageLimit = messageLimit; + } } From fea602a0a4c89fc991a4286e2468670f614b4183 Mon Sep 17 00:00:00 2001 From: Rob Greene Date: Sun, 13 Mar 2022 20:52:33 -0500 Subject: [PATCH 3/7] 'acx' returns non-zero code if disks do not match. --- .../io/github/applecommander/acx/command/CompareCommand.java | 1 + 1 file changed, 1 insertion(+) diff --git a/app/cli-acx/src/main/java/io/github/applecommander/acx/command/CompareCommand.java b/app/cli-acx/src/main/java/io/github/applecommander/acx/command/CompareCommand.java index b3d5cba..df9e849 100644 --- a/app/cli-acx/src/main/java/io/github/applecommander/acx/command/CompareCommand.java +++ b/app/cli-acx/src/main/java/io/github/applecommander/acx/command/CompareCommand.java @@ -61,6 +61,7 @@ public class CompareCommand extends ReadOnlyDiskImageCommandOptions { if (result.getDifferenceCount() > limit.orElse(Integer.MAX_VALUE)) { System.out.printf("There are %d more messages.\n", result.getDifferenceCount() - limit.get()); } + return 1; } return 0; From 9a0a0348491d0b5dd65b72c9808f2786a1aa2053 Mon Sep 17 00:00:00 2001 From: Rob Greene Date: Sun, 13 Mar 2022 20:59:09 -0500 Subject: [PATCH 4/7] Removing debugging line. --- .../java/com/webcodepro/applecommander/ui/swt/wizard/Wizard.java | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/ac-swt-common/src/main/java/com/webcodepro/applecommander/ui/swt/wizard/Wizard.java b/lib/ac-swt-common/src/main/java/com/webcodepro/applecommander/ui/swt/wizard/Wizard.java index 4465bdf..022d2ce 100644 --- a/lib/ac-swt-common/src/main/java/com/webcodepro/applecommander/ui/swt/wizard/Wizard.java +++ b/lib/ac-swt-common/src/main/java/com/webcodepro/applecommander/ui/swt/wizard/Wizard.java @@ -83,7 +83,6 @@ public abstract class Wizard { RowData rowData = new RowData(); rowData.width = logo.getImageData().width; rowData.height = logo.getImageData().height; - System.out.println(rowData.toString()); imageCanvas = new ImageCanvas(dialog, SWT.BORDER, logo, rowData); // Starting pane From fdb4a6d5660ffd85668b73818bccae719c44a491 Mon Sep 17 00:00:00 2001 From: Rob Greene Date: Mon, 14 Mar 2022 20:15:51 -0500 Subject: [PATCH 5/7] Adding Range to better report a condensed description of differences. --- .../storage/compare/DiskDiff.java | 24 +++-- .../webcodepro/applecommander/util/Range.java | 89 +++++++++++++++++++ .../applecommander/util/RangeTest.java | 49 ++++++++++ 3 files changed, 157 insertions(+), 5 deletions(-) create mode 100644 lib/ac-api/src/main/java/com/webcodepro/applecommander/util/Range.java create mode 100644 lib/ac-api/src/test/java/com/webcodepro/applecommander/util/RangeTest.java diff --git a/lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/compare/DiskDiff.java b/lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/compare/DiskDiff.java index e580a93..ba3e9d2 100644 --- a/lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/compare/DiskDiff.java +++ b/lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/compare/DiskDiff.java @@ -24,12 +24,14 @@ import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.function.BiConsumer; +import java.util.stream.Collectors; import com.webcodepro.applecommander.storage.Disk; import com.webcodepro.applecommander.storage.DiskGeometry; import com.webcodepro.applecommander.storage.DiskUnrecognizedException; import com.webcodepro.applecommander.storage.FormattedDisk; import com.webcodepro.applecommander.storage.physical.ImageOrder; +import com.webcodepro.applecommander.util.Range; /** * Perform a disk comparison based on selected strategy. @@ -123,12 +125,21 @@ public class DiskDiff { orderA.getBlocksOnDevice(), orderB.getBlocksOnDevice()); return; } - + + List unequalBlocks = new ArrayList<>(); for (int block=0; block unequalSectors = new ArrayList<>(); + List unequalSectors = new ArrayList<>(); for (int sector=0; sector from(List numbers) { + List ranges = new ArrayList<>(); + Collections.sort(numbers); + + int first = -1; + int last = -1; + for (int number : numbers) { + if (first == -1) { + first = last = number; + } + else if (number == last+1) { + last = number; + } + else { + ranges.add(new Range(first, last)); + first = last = number; + } + } + + if (first != -1) { + ranges.add(new Range(first, last)); + } + + return ranges; + } +} diff --git a/lib/ac-api/src/test/java/com/webcodepro/applecommander/util/RangeTest.java b/lib/ac-api/src/test/java/com/webcodepro/applecommander/util/RangeTest.java new file mode 100644 index 0000000..1e7f4bb --- /dev/null +++ b/lib/ac-api/src/test/java/com/webcodepro/applecommander/util/RangeTest.java @@ -0,0 +1,49 @@ +/* + * AppleCommander - An Apple ][ image utility. + * Copyright (C) 2002-2022 by Robert Greene and others + * robgreene at users.sourceforge.net + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package com.webcodepro.applecommander.util; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; + +import org.junit.Test; + +public class RangeTest { + @Test + public void testToString() { + assertEquals("1", new Range(1,1).toString()); + assertEquals("1-5", new Range(1,5).toString()); + } + + @Test + public void testFrom() { + assertEquals("[1-3, 5, 7, 9-12]", Range.from(Arrays.asList(1,2,3,5,7,9,10,11,12)).toString()); + } + + @Test + public void testFromUnordered() { + assertEquals("[1-3, 5, 7, 9-12]", Range.from(Arrays.asList(9,10,1,5,2,12,3,11,7)).toString()); + } + + @Test + public void testFromEmpty() { + assertEquals("[]", Range.from(Arrays.asList()).toString()); + } +} From e6cbda99089367fc119eb8493d0f4267d0b08cf9 Mon Sep 17 00:00:00 2001 From: Rob Greene Date: Thu, 17 Mar 2022 21:19:43 -0500 Subject: [PATCH 6/7] Adding filename disk comparison strategy. --- .../acx/command/CompareCommand.java | 7 ++ .../acx/command/ImportCommand.java | 4 +- .../acx/fileutil/FileUtils.java | 2 + .../storage/compare/DiskDiff.java | 111 ++++++++++++++++++ .../util/filestreamer/FileStreamer.java | 8 +- .../util/filestreamer/FileTuple.java | 4 + .../DosFileEntryReaderWriter.java | 2 +- .../util/readerwriter}/FileEntryReader.java | 16 ++- .../util/readerwriter}/FileEntryWriter.java | 2 +- .../readerwriter}/NakedosFileEntryReader.java | 2 +- .../OverrideFileEntryReader.java | 2 +- .../PascalFileEntryReaderWriter.java | 2 +- .../ProdosFileEntryReaderWriter.java | 2 +- .../readerwriter}/RdosFileEntryReader.java | 2 +- .../comparedisks/CompareDisksResultsPane.java | 19 ++- .../comparedisks/CompareDisksStartPane.java | 22 +++- .../comparedisks/CompareDisksWizard.java | 7 ++ 17 files changed, 200 insertions(+), 14 deletions(-) rename {app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil => lib/ac-api/src/main/java/com/webcodepro/applecommander/util/readerwriter}/DosFileEntryReaderWriter.java (98%) rename {app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil => lib/ac-api/src/main/java/com/webcodepro/applecommander/util/readerwriter}/FileEntryReader.java (80%) rename {app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil => lib/ac-api/src/main/java/com/webcodepro/applecommander/util/readerwriter}/FileEntryWriter.java (98%) rename {app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil => lib/ac-api/src/main/java/com/webcodepro/applecommander/util/readerwriter}/NakedosFileEntryReader.java (96%) rename {app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil => lib/ac-api/src/main/java/com/webcodepro/applecommander/util/readerwriter}/OverrideFileEntryReader.java (99%) rename {app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil => lib/ac-api/src/main/java/com/webcodepro/applecommander/util/readerwriter}/PascalFileEntryReaderWriter.java (98%) rename {app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil => lib/ac-api/src/main/java/com/webcodepro/applecommander/util/readerwriter}/ProdosFileEntryReaderWriter.java (98%) rename {app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil => lib/ac-api/src/main/java/com/webcodepro/applecommander/util/readerwriter}/RdosFileEntryReader.java (97%) diff --git a/app/cli-acx/src/main/java/io/github/applecommander/acx/command/CompareCommand.java b/app/cli-acx/src/main/java/io/github/applecommander/acx/command/CompareCommand.java index df9e849..ecb1e48 100644 --- a/app/cli-acx/src/main/java/io/github/applecommander/acx/command/CompareCommand.java +++ b/app/cli-acx/src/main/java/io/github/applecommander/acx/command/CompareCommand.java @@ -82,6 +82,10 @@ public class CompareCommand extends ReadOnlyDiskImageCommandOptions { private void selectTrackSectorGeometry(boolean flag) { strategy = this::trackSectorGeometry; } + @Option(names = { "--filename" }, description = "Compare by filename.") + private void selectByFilename(boolean flag) { + strategy = this::filename; + } private void nativeGeometry(DiskDiff.Builder builder) { builder.selectCompareByNativeGeometry(); @@ -92,5 +96,8 @@ public class CompareCommand extends ReadOnlyDiskImageCommandOptions { private void trackSectorGeometry(DiskDiff.Builder builder) { builder.selectCompareByTrackSectorGeometry(); } + private void filename(DiskDiff.Builder builder) { + builder.selectCompareByFileName(); + } } } diff --git a/app/cli-acx/src/main/java/io/github/applecommander/acx/command/ImportCommand.java b/app/cli-acx/src/main/java/io/github/applecommander/acx/command/ImportCommand.java index a7fda41..75efa07 100644 --- a/app/cli-acx/src/main/java/io/github/applecommander/acx/command/ImportCommand.java +++ b/app/cli-acx/src/main/java/io/github/applecommander/acx/command/ImportCommand.java @@ -41,15 +41,15 @@ import com.webcodepro.applecommander.storage.os.prodos.ProdosFormatDisk; import com.webcodepro.applecommander.util.AppleUtil; import com.webcodepro.applecommander.util.StreamUtil; import com.webcodepro.applecommander.util.TranslatorStream; +import com.webcodepro.applecommander.util.readerwriter.FileEntryReader; +import com.webcodepro.applecommander.util.readerwriter.OverrideFileEntryReader; import com.webcodepro.shrinkit.HeaderBlock; import com.webcodepro.shrinkit.NuFileArchive; import com.webcodepro.shrinkit.ThreadRecord; import io.github.applecommander.acx.base.ReadWriteDiskCommandOptions; import io.github.applecommander.acx.converter.IntegerTypeConverter; -import io.github.applecommander.acx.fileutil.FileEntryReader; import io.github.applecommander.acx.fileutil.FileUtils; -import io.github.applecommander.acx.fileutil.OverrideFileEntryReader; import io.github.applecommander.applesingle.AppleSingle; import io.github.applecommander.applesingle.FileDatesInfo; import io.github.applecommander.applesingle.ProdosFileInfo; diff --git a/app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil/FileUtils.java b/app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil/FileUtils.java index f9979fb..9f21e5f 100644 --- a/app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil/FileUtils.java +++ b/app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil/FileUtils.java @@ -25,6 +25,8 @@ import java.util.logging.Logger; import com.webcodepro.applecommander.storage.DirectoryEntry; import com.webcodepro.applecommander.storage.DiskException; import com.webcodepro.applecommander.storage.FileEntry; +import com.webcodepro.applecommander.util.readerwriter.FileEntryReader; +import com.webcodepro.applecommander.util.readerwriter.FileEntryWriter; import io.github.applecommander.acx.command.CopyFileCommand; diff --git a/lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/compare/DiskDiff.java b/lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/compare/DiskDiff.java index ba3e9d2..91a3e74 100644 --- a/lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/compare/DiskDiff.java +++ b/lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/compare/DiskDiff.java @@ -21,17 +21,25 @@ package com.webcodepro.applecommander.storage.compare; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.function.BiConsumer; import java.util.stream.Collectors; import com.webcodepro.applecommander.storage.Disk; +import com.webcodepro.applecommander.storage.DiskException; import com.webcodepro.applecommander.storage.DiskGeometry; import com.webcodepro.applecommander.storage.DiskUnrecognizedException; import com.webcodepro.applecommander.storage.FormattedDisk; import com.webcodepro.applecommander.storage.physical.ImageOrder; import com.webcodepro.applecommander.util.Range; +import com.webcodepro.applecommander.util.filestreamer.FileStreamer; +import com.webcodepro.applecommander.util.filestreamer.FileTuple; +import com.webcodepro.applecommander.util.filestreamer.TypeOfFile; +import com.webcodepro.applecommander.util.readerwriter.FileEntryReader; /** * Perform a disk comparison based on selected strategy. @@ -174,6 +182,99 @@ public class DiskDiff { } } + public void compareByFileName(FormattedDisk formattedDiskA, FormattedDisk formattedDiskB) { + try { + Map> filesA = FileStreamer.forDisk(formattedDiskA) + .includeTypeOfFile(TypeOfFile.FILE) + .recursive(true) + .stream() + .collect(Collectors.groupingBy(FileTuple::fullPath)); + Map> filesB = FileStreamer.forDisk(formattedDiskB) + .includeTypeOfFile(TypeOfFile.FILE) + .recursive(true) + .stream() + .collect(Collectors.groupingBy(FileTuple::fullPath)); + + Set pathsOnlyA = new HashSet<>(filesA.keySet()); + pathsOnlyA.removeAll(filesB.keySet()); + if (!pathsOnlyA.isEmpty()) { + results.addError("Files only in %s: %s", formattedDiskA.getFilename(), String.join(", ", pathsOnlyA)); + } + + Set pathsOnlyB = new HashSet<>(filesB.keySet()); + pathsOnlyB.removeAll(filesA.keySet()); + if (!pathsOnlyB.isEmpty()) { + results.addError("Files only in %s: %s", formattedDiskB.getFilename(), String.join(", ", pathsOnlyB)); + } + + Set pathsInAB = new HashSet<>(filesA.keySet()); + pathsInAB.retainAll(filesB.keySet()); + for (String path : pathsInAB) { + List tuplesA = filesA.get(path); + List tuplesB = filesB.get(path); + + // Since this is by name, we expect a single file; report oddities + FileTuple tupleA = tuplesA.get(0); + if (tuplesA.size() > 1) { + results.addWarning("Path %s on disk %s has %d entries.", path, formattedDiskA.getFilename(), tuplesA.size()); + } + FileTuple tupleB = tuplesB.get(0); + if (tuplesB.size() > 1) { + results.addWarning("Path %s on disk %s has %d entries.", path, formattedDiskB.getFilename(), tuplesB.size()); + } + + // Do our own custom compare so we can capture a description of differences: + FileEntryReader readerA = FileEntryReader.get(tupleA.fileEntry); + FileEntryReader readerB = FileEntryReader.get(tupleB.fileEntry); + List differences = compare(readerA, readerB); + if (!differences.isEmpty()) { + results.addWarning("Path %s differ: %s", path, String.join(", ", differences)); + } + } + } catch (DiskException ex) { + results.addError(ex); + } + } + + public void compareByFileContent(FormattedDisk formattedDiskA, FormattedDisk formattedDiskB) { + // TODO + } + + private List compare(FileEntryReader readerA, FileEntryReader readerB) { + List differences = new ArrayList<>(); + if (!readerA.getFilename().equals(readerB.getFilename())) { + differences.add("filename"); + } + if (!readerA.getProdosFiletype().equals(readerB.getProdosFiletype())) { + differences.add("filetype"); + } + if (!readerA.isLocked().equals(readerB.isLocked())) { + differences.add("locked"); + } + if (!Arrays.equals(readerA.getFileData().orElse(null), readerB.getFileData().orElse(null))) { + differences.add("file data"); + } + if (!Arrays.equals(readerA.getResourceData().orElse(null), readerB.getResourceData().orElse(null))) { + differences.add("resource fork"); + } + if (!readerA.getBinaryAddress().equals(readerB.getBinaryAddress())) { + differences.add("address"); + } + if (!readerA.getBinaryLength().equals(readerB.getBinaryLength())) { + differences.add("length"); + } + if (!readerA.getAuxiliaryType().equals(readerB.getAuxiliaryType())) { + differences.add("aux. type"); + } + if (!readerA.getCreationDate().equals(readerB.getCreationDate())) { + differences.add("create date"); + } + if (!readerA.getLastModificationDate().equals(readerB.getLastModificationDate())) { + differences.add("mod. date"); + } + return differences; + } + public static class Builder { private DiskDiff diff; @@ -195,6 +296,16 @@ public class DiskDiff { diff.diskComparisonStrategy = diff::compareByBlockGeometry; return this; } + /** Compare disks by files ensuring that all filenames match. */ + public Builder selectCompareByFileName() { + diff.diskComparisonStrategy = diff::compareByFileName; + return this; + } + /** Compare disks by files based on content; allowing files to have moved or been renamed. */ + public Builder selectCompareByFileContent() { + diff.diskComparisonStrategy = diff::compareByFileContent; + return this; + } public ComparisonResult compare() { return diff.compare(); diff --git a/lib/ac-api/src/main/java/com/webcodepro/applecommander/util/filestreamer/FileStreamer.java b/lib/ac-api/src/main/java/com/webcodepro/applecommander/util/filestreamer/FileStreamer.java index 37a1a82..8d19380 100644 --- a/lib/ac-api/src/main/java/com/webcodepro/applecommander/util/filestreamer/FileStreamer.java +++ b/lib/ac-api/src/main/java/com/webcodepro/applecommander/util/filestreamer/FileStreamer.java @@ -72,6 +72,9 @@ public class FileStreamer { public static FileStreamer forDisk(Disk disk) throws DiskUnrecognizedException { return new FileStreamer(disk); } + public static FileStreamer forFormattedDisks(FormattedDisk... disks) { + return new FileStreamer(disks); + } private FormattedDisk[] formattedDisks = null; @@ -89,7 +92,10 @@ public class FileStreamer { private List pathMatchers = new ArrayList<>(); private FileStreamer(Disk disk) throws DiskUnrecognizedException { - this.formattedDisks = disk.getFormattedDisks(); + this(disk.getFormattedDisks()); + } + private FileStreamer(FormattedDisk... disks) { + this.formattedDisks = disks; } public FileStreamer ignoreErrors(boolean flag) { diff --git a/lib/ac-api/src/main/java/com/webcodepro/applecommander/util/filestreamer/FileTuple.java b/lib/ac-api/src/main/java/com/webcodepro/applecommander/util/filestreamer/FileTuple.java index b87dc8a..9bd9a1f 100644 --- a/lib/ac-api/src/main/java/com/webcodepro/applecommander/util/filestreamer/FileTuple.java +++ b/lib/ac-api/src/main/java/com/webcodepro/applecommander/util/filestreamer/FileTuple.java @@ -29,6 +29,7 @@ import com.webcodepro.applecommander.storage.FileEntry; import com.webcodepro.applecommander.storage.FormattedDisk; public class FileTuple { + public static final String SEPARATOR = "/"; private static final Logger LOG = Logger.getLogger(FileTuple.class.getName()); public final FormattedDisk formattedDisk; public final List paths; @@ -54,6 +55,9 @@ public class FileTuple { public FileTuple of(FileEntry fileEntry) { return new FileTuple(formattedDisk, paths, directoryEntry, fileEntry); } + public String fullPath() { + return String.join(SEPARATOR, String.join(SEPARATOR, paths), fileEntry.getFilename()); + } public static FileTuple of(FormattedDisk disk) { return new FileTuple(disk, new ArrayList(), (DirectoryEntry)disk, null); diff --git a/app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil/DosFileEntryReaderWriter.java b/lib/ac-api/src/main/java/com/webcodepro/applecommander/util/readerwriter/DosFileEntryReaderWriter.java similarity index 98% rename from app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil/DosFileEntryReaderWriter.java rename to lib/ac-api/src/main/java/com/webcodepro/applecommander/util/readerwriter/DosFileEntryReaderWriter.java index dbb55a7..c57a11f 100644 --- a/app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil/DosFileEntryReaderWriter.java +++ b/lib/ac-api/src/main/java/com/webcodepro/applecommander/util/readerwriter/DosFileEntryReaderWriter.java @@ -17,7 +17,7 @@ * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -package io.github.applecommander.acx.fileutil; +package com.webcodepro.applecommander.util.readerwriter; import java.util.Map; import java.util.Optional; diff --git a/app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil/FileEntryReader.java b/lib/ac-api/src/main/java/com/webcodepro/applecommander/util/readerwriter/FileEntryReader.java similarity index 80% rename from app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil/FileEntryReader.java rename to lib/ac-api/src/main/java/com/webcodepro/applecommander/util/readerwriter/FileEntryReader.java index 1eaccf3..bdaa656 100644 --- a/app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil/FileEntryReader.java +++ b/lib/ac-api/src/main/java/com/webcodepro/applecommander/util/readerwriter/FileEntryReader.java @@ -17,8 +17,9 @@ * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -package io.github.applecommander.acx.fileutil; +package com.webcodepro.applecommander.util.readerwriter; +import java.util.Arrays; import java.util.Date; import java.util.Optional; @@ -52,6 +53,19 @@ public interface FileEntryReader { // ProdosFileEntry / PascalFileEntry specific public default Optional getLastModificationDate() { return Optional.empty(); } + public default boolean equals(FileEntryReader reader) { + return getFilename().equals(reader.getFilename()) + && getProdosFiletype().equals(reader.getProdosFiletype()) + && isLocked().equals(reader.isLocked()) + && Arrays.equals(getFileData().orElse(null), reader.getFileData().orElse(null)) + && Arrays.equals(getResourceData().orElse(null), reader.getResourceData().orElse(null)) + && getBinaryAddress().equals(reader.getBinaryAddress()) + && getBinaryLength().equals(reader.getBinaryLength()) + && getAuxiliaryType().equals(reader.getAuxiliaryType()) + && getCreationDate().equals(reader.getCreationDate()) + && getLastModificationDate().equals(reader.getLastModificationDate()); + } + public static FileEntryReader get(FileEntry fileEntry) { if (fileEntry instanceof DosFileEntry) { return new DosFileEntryReaderWriter((DosFileEntry)fileEntry); diff --git a/app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil/FileEntryWriter.java b/lib/ac-api/src/main/java/com/webcodepro/applecommander/util/readerwriter/FileEntryWriter.java similarity index 98% rename from app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil/FileEntryWriter.java rename to lib/ac-api/src/main/java/com/webcodepro/applecommander/util/readerwriter/FileEntryWriter.java index dc5981f..8114137 100644 --- a/app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil/FileEntryWriter.java +++ b/lib/ac-api/src/main/java/com/webcodepro/applecommander/util/readerwriter/FileEntryWriter.java @@ -17,7 +17,7 @@ * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -package io.github.applecommander.acx.fileutil; +package com.webcodepro.applecommander.util.readerwriter; import java.util.Date; diff --git a/app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil/NakedosFileEntryReader.java b/lib/ac-api/src/main/java/com/webcodepro/applecommander/util/readerwriter/NakedosFileEntryReader.java similarity index 96% rename from app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil/NakedosFileEntryReader.java rename to lib/ac-api/src/main/java/com/webcodepro/applecommander/util/readerwriter/NakedosFileEntryReader.java index 1af8192..dea8c2d 100644 --- a/app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil/NakedosFileEntryReader.java +++ b/lib/ac-api/src/main/java/com/webcodepro/applecommander/util/readerwriter/NakedosFileEntryReader.java @@ -17,7 +17,7 @@ * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -package io.github.applecommander.acx.fileutil; +package com.webcodepro.applecommander.util.readerwriter; import java.util.Optional; diff --git a/app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil/OverrideFileEntryReader.java b/lib/ac-api/src/main/java/com/webcodepro/applecommander/util/readerwriter/OverrideFileEntryReader.java similarity index 99% rename from app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil/OverrideFileEntryReader.java rename to lib/ac-api/src/main/java/com/webcodepro/applecommander/util/readerwriter/OverrideFileEntryReader.java index cec591a..7413d0b 100644 --- a/app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil/OverrideFileEntryReader.java +++ b/lib/ac-api/src/main/java/com/webcodepro/applecommander/util/readerwriter/OverrideFileEntryReader.java @@ -17,7 +17,7 @@ * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -package io.github.applecommander.acx.fileutil; +package com.webcodepro.applecommander.util.readerwriter; import java.util.Arrays; import java.util.Date; diff --git a/app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil/PascalFileEntryReaderWriter.java b/lib/ac-api/src/main/java/com/webcodepro/applecommander/util/readerwriter/PascalFileEntryReaderWriter.java similarity index 98% rename from app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil/PascalFileEntryReaderWriter.java rename to lib/ac-api/src/main/java/com/webcodepro/applecommander/util/readerwriter/PascalFileEntryReaderWriter.java index 5c57f66..f521a40 100644 --- a/app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil/PascalFileEntryReaderWriter.java +++ b/lib/ac-api/src/main/java/com/webcodepro/applecommander/util/readerwriter/PascalFileEntryReaderWriter.java @@ -17,7 +17,7 @@ * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -package io.github.applecommander.acx.fileutil; +package com.webcodepro.applecommander.util.readerwriter; import java.util.Date; import java.util.Map; diff --git a/app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil/ProdosFileEntryReaderWriter.java b/lib/ac-api/src/main/java/com/webcodepro/applecommander/util/readerwriter/ProdosFileEntryReaderWriter.java similarity index 98% rename from app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil/ProdosFileEntryReaderWriter.java rename to lib/ac-api/src/main/java/com/webcodepro/applecommander/util/readerwriter/ProdosFileEntryReaderWriter.java index 7fa71a9..a55d089 100644 --- a/app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil/ProdosFileEntryReaderWriter.java +++ b/lib/ac-api/src/main/java/com/webcodepro/applecommander/util/readerwriter/ProdosFileEntryReaderWriter.java @@ -17,7 +17,7 @@ * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -package io.github.applecommander.acx.fileutil; +package com.webcodepro.applecommander.util.readerwriter; import java.util.Date; import java.util.Optional; diff --git a/app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil/RdosFileEntryReader.java b/lib/ac-api/src/main/java/com/webcodepro/applecommander/util/readerwriter/RdosFileEntryReader.java similarity index 97% rename from app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil/RdosFileEntryReader.java rename to lib/ac-api/src/main/java/com/webcodepro/applecommander/util/readerwriter/RdosFileEntryReader.java index 5cdd7ee..15fefb8 100644 --- a/app/cli-acx/src/main/java/io/github/applecommander/acx/fileutil/RdosFileEntryReader.java +++ b/lib/ac-api/src/main/java/com/webcodepro/applecommander/util/readerwriter/RdosFileEntryReader.java @@ -17,7 +17,7 @@ * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -package io.github.applecommander.acx.fileutil; +package com.webcodepro.applecommander.util.readerwriter; import java.util.Map; import java.util.Optional; diff --git a/lib/ac-swt-common/src/main/java/com/webcodepro/applecommander/ui/swt/wizard/comparedisks/CompareDisksResultsPane.java b/lib/ac-swt-common/src/main/java/com/webcodepro/applecommander/ui/swt/wizard/comparedisks/CompareDisksResultsPane.java index b42c3f7..6804068 100644 --- a/lib/ac-swt-common/src/main/java/com/webcodepro/applecommander/ui/swt/wizard/comparedisks/CompareDisksResultsPane.java +++ b/lib/ac-swt-common/src/main/java/com/webcodepro/applecommander/ui/swt/wizard/comparedisks/CompareDisksResultsPane.java @@ -116,7 +116,24 @@ public class CompareDisksResultsPane extends WizardPane { 2, t.getLocalizedMessage())); } if (disk1 != null && disk2 != null) { - ComparisonResult result = DiskDiff.compare(disk1, disk2); + DiskDiff.Builder builder = DiskDiff.create(disk1, disk2); + switch (wizard.getComparisonStrategy()) { + case 0: + builder.selectCompareByNativeGeometry(); + break; + case 1: + builder.selectCompareByTrackSectorGeometry(); + break; + case 2: + builder.selectCompareByBlockGeometry(); + break; + case 3: + builder.selectCompareByFileName(); + break; + default: + throw new RuntimeException("missing a comparison strategy"); + } + ComparisonResult result = builder.compare(); errorMessages.addAll(result.getLimitedMessages(wizard.getMessageLimit())); } if (errorMessages.size() == 0) { diff --git a/lib/ac-swt-common/src/main/java/com/webcodepro/applecommander/ui/swt/wizard/comparedisks/CompareDisksStartPane.java b/lib/ac-swt-common/src/main/java/com/webcodepro/applecommander/ui/swt/wizard/comparedisks/CompareDisksStartPane.java index fab4c27..8563c18 100644 --- a/lib/ac-swt-common/src/main/java/com/webcodepro/applecommander/ui/swt/wizard/comparedisks/CompareDisksStartPane.java +++ b/lib/ac-swt-common/src/main/java/com/webcodepro/applecommander/ui/swt/wizard/comparedisks/CompareDisksStartPane.java @@ -27,6 +27,7 @@ import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.layout.RowData; import org.eclipse.swt.layout.RowLayout; import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.FileDialog; import org.eclipse.swt.widgets.Label; @@ -49,6 +50,7 @@ public class CompareDisksStartPane extends WizardPane { private CompareDisksWizard wizard; private Text diskname1Text; private Text diskname2Text; + private Combo comparisonStrategyCombo; private Text limitText; /** * Constructor for CompareDisksStartPane. @@ -133,6 +135,22 @@ public class CompareDisksStartPane extends WizardPane { } } }); + + label = new Label(control, SWT.WRAP); + label.setText("Select comparison time:"); + + comparisonStrategyCombo = new Combo(control, SWT.BORDER | SWT.READ_ONLY); + comparisonStrategyCombo.setItems("Compare by native geometry", + "Compare by track/sector geometry", + "Compare by block geometry", + "Compare by filename"); + comparisonStrategyCombo.select(getWizard().getComparisonStrategy()); + comparisonStrategyCombo.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + getWizard().setComparisonStrategy(comparisonStrategyCombo.getSelectionIndex()); + } + }); label = new Label(control, SWT.WRAP); label.setText("Set limit on messages displayed:"); @@ -178,9 +196,9 @@ public class CompareDisksStartPane extends WizardPane { protected void limitTextModifyListener(ModifyEvent event) { try { - wizard.setMessageLimit(Integer.parseInt(limitText.getText())); + getWizard().setMessageLimit(Integer.parseInt(limitText.getText())); } catch (NumberFormatException e) { - limitText.setText(Integer.toString(wizard.getMessageLimit())); + limitText.setText(Integer.toString(getWizard().getMessageLimit())); } } } diff --git a/lib/ac-swt-common/src/main/java/com/webcodepro/applecommander/ui/swt/wizard/comparedisks/CompareDisksWizard.java b/lib/ac-swt-common/src/main/java/com/webcodepro/applecommander/ui/swt/wizard/comparedisks/CompareDisksWizard.java index da3acb6..abdb5c2 100644 --- a/lib/ac-swt-common/src/main/java/com/webcodepro/applecommander/ui/swt/wizard/comparedisks/CompareDisksWizard.java +++ b/lib/ac-swt-common/src/main/java/com/webcodepro/applecommander/ui/swt/wizard/comparedisks/CompareDisksWizard.java @@ -34,6 +34,7 @@ import com.webcodepro.applecommander.ui.swt.wizard.WizardPane; public class CompareDisksWizard extends Wizard { private String diskname1; private String diskname2; + private int comparisonStrategy = 0; private int messageLimit = 10; /** * Constructor for ExportWizard. @@ -55,6 +56,9 @@ public class CompareDisksWizard extends Wizard { public String getDiskname2() { return diskname2; } + public int getComparisonStrategy() { + return comparisonStrategy; + } public int getMessageLimit() { return messageLimit; } @@ -64,6 +68,9 @@ public class CompareDisksWizard extends Wizard { public void setDiskname2(String string) { diskname2 = string; } + public void setComparisonStrategy(int comparisonStrategy) { + this.comparisonStrategy = comparisonStrategy; + } public void setMessageLimit(int messageLimit) { this.messageLimit = messageLimit; } From d9f6653163ef85eb8fb5430631ee19342148e537 Mon Sep 17 00:00:00 2001 From: Rob Greene Date: Sat, 19 Mar 2022 22:33:05 -0500 Subject: [PATCH 7/7] Adding compare by content. --- .../acx/command/CompareCommand.java | 7 ++ .../storage/compare/DiskDiff.java | 81 ++++++++++++++++++- 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/app/cli-acx/src/main/java/io/github/applecommander/acx/command/CompareCommand.java b/app/cli-acx/src/main/java/io/github/applecommander/acx/command/CompareCommand.java index ecb1e48..fa2e26d 100644 --- a/app/cli-acx/src/main/java/io/github/applecommander/acx/command/CompareCommand.java +++ b/app/cli-acx/src/main/java/io/github/applecommander/acx/command/CompareCommand.java @@ -86,6 +86,10 @@ public class CompareCommand extends ReadOnlyDiskImageCommandOptions { private void selectByFilename(boolean flag) { strategy = this::filename; } + @Option(names = { "--content" }, description = "Compare by file content.") + private void selectByFileContent(boolean flag) { + strategy = this::fileContent; + } private void nativeGeometry(DiskDiff.Builder builder) { builder.selectCompareByNativeGeometry(); @@ -99,5 +103,8 @@ public class CompareCommand extends ReadOnlyDiskImageCommandOptions { private void filename(DiskDiff.Builder builder) { builder.selectCompareByFileName(); } + private void fileContent(DiskDiff.Builder builder) { + builder.selectCompareByFileContent(); + } } } diff --git a/lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/compare/DiskDiff.java b/lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/compare/DiskDiff.java index 91a3e74..25d523f 100644 --- a/lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/compare/DiskDiff.java +++ b/lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/compare/DiskDiff.java @@ -19,6 +19,9 @@ */ package com.webcodepro.applecommander.storage.compare; +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -182,6 +185,7 @@ public class DiskDiff { } } + /** Compare by filename. This accounts for names only in disk A, only in disk B, or different but same-named. */ public void compareByFileName(FormattedDisk formattedDiskA, FormattedDisk formattedDiskB) { try { Map> filesA = FileStreamer.forDisk(formattedDiskA) @@ -236,8 +240,83 @@ public class DiskDiff { } } + /** Compare by file content. Accounts for content differences that are "only" in disk A or "only" in disk B. */ public void compareByFileContent(FormattedDisk formattedDiskA, FormattedDisk formattedDiskB) { - // TODO + try { + Map> contentA = FileStreamer.forDisk(formattedDiskA) + .includeTypeOfFile(TypeOfFile.FILE) + .recursive(true) + .stream() + .collect(Collectors.groupingBy(this::contentHash)); + Map> contentB = FileStreamer.forDisk(formattedDiskB) + .includeTypeOfFile(TypeOfFile.FILE) + .recursive(true) + .stream() + .collect(Collectors.groupingBy(this::contentHash)); + + Set contentOnlyA = new HashSet<>(contentA.keySet()); + contentOnlyA.removeAll(contentB.keySet()); + if (!contentOnlyA.isEmpty()) { + Set pathNamesA = contentOnlyA.stream() + .map(contentA::get) + .flatMap(List::stream) + .map(FileTuple::fullPath) + .collect(Collectors.toSet()); + results.addError("Content that only exists in %s: %s", + formattedDiskA.getFilename(), String.join(", ", pathNamesA)); + } + + Set contentOnlyB = new HashSet<>(contentB.keySet()); + contentOnlyB.removeAll(contentA.keySet()); + if (!contentOnlyB.isEmpty()) { + Set pathNamesB = contentOnlyB.stream() + .map(contentB::get) + .flatMap(List::stream) + .map(FileTuple::fullPath) + .collect(Collectors.toSet()); + results.addError("Content that only exists in %s: %s", + formattedDiskB.getFilename(), String.join(", ", pathNamesB)); + } + + Set contentInAB = new HashSet<>(contentA.keySet()); + contentInAB.retainAll(contentB.keySet()); + for (String content : contentInAB) { + List tuplesA = contentA.get(content); + List tuplesB = contentB.get(content); + + // This is by content, but uncertain how to report multiple per disk, so pick first one + FileTuple tupleA = tuplesA.get(0); + if (tuplesA.size() > 1) { + results.addWarning("Hash %s on disk %s has %d entries.", content, + formattedDiskA.getFilename(), tuplesA.size()); + } + FileTuple tupleB = tuplesB.get(0); + if (tuplesB.size() > 1) { + results.addWarning("Hash %s on disk %s has %d entries.", content, + formattedDiskB.getFilename(), tuplesB.size()); + } + + // Do our own custom compare so we can capture a description of differences: + FileEntryReader readerA = FileEntryReader.get(tupleA.fileEntry); + FileEntryReader readerB = FileEntryReader.get(tupleB.fileEntry); + List differences = compare(readerA, readerB); + if (!differences.isEmpty()) { + results.addWarning("Files %s and %s share same content but file attributes differ: %s", + tupleA.fullPath(), tupleB.fullPath(), String.join(", ", differences)); + } + } + } catch (DiskException ex) { + results.addError(ex); + } + } + private String contentHash(FileTuple tuple) { + try { + MessageDigest messageDigest = MessageDigest.getInstance("MD5"); + byte[] digest = messageDigest.digest(tuple.fileEntry.getFileData()); + return String.format("%032X", new BigInteger(1, digest)); + } catch (NoSuchAlgorithmException ex) { + throw new RuntimeException(ex); + } } private List compare(FileEntryReader readerA, FileEntryReader readerB) {