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); } }