Merge pull request #78 from AppleCommander/feature/expand_compare_capabilities

Feature/expand compare capabilities #48
This commit is contained in:
A2 Geek 2022-03-19 22:41:59 -05:00 committed by GitHub
commit 7bb1408b3a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 942 additions and 97 deletions

View File

@ -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,

View File

@ -0,0 +1,110 @@
/*
* 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.Optional;
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();
@Option(names = { "-l", "--limit" }, description = "Set limit to messages displayed.")
private Optional<Integer> limit = Optional.empty();
@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.");
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 1;
}
return 0;
}
public static class StrategySelection {
private Consumer<DiskDiff.Builder> 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;
}
@Option(names = { "--filename" }, description = "Compare by filename.")
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();
}
private void blockGeometry(DiskDiff.Builder builder) {
builder.selectCompareByBlockGeometry();
}
private void trackSectorGeometry(DiskDiff.Builder builder) {
builder.selectCompareByTrackSectorGeometry();
}
private void filename(DiskDiff.Builder builder) {
builder.selectCompareByFileName();
}
private void fileContent(DiskDiff.Builder builder) {
builder.selectCompareByFileContent();
}
}
}

View File

@ -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;

View File

@ -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;

View File

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

View File

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

View File

@ -0,0 +1,63 @@
/*
* 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<String> errors = new ArrayList<>();
private List<String> warnings = new ArrayList<>();
public boolean hasErrors() {
return !errors.isEmpty();
}
public int getDifferenceCount() {
return errors.size() + warnings.size();
}
public List<String> getAllMessages() {
List<String> messages = new ArrayList<>();
messages.addAll(errors);
messages.addAll(warnings);
return messages;
}
public List<String> getLimitedMessages(int limit) {
List<String> messages = getAllMessages();
return messages.subList(0, Math.min(messages.size(), limit));
}
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<String> getErrors() {
return errors;
}
public List<String> getWarnings() {
return warnings;
}
}

View File

@ -0,0 +1,393 @@
/*
* 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.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
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.
*/
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<FormattedDisk,FormattedDisk> 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<min; i++) {
this.diskComparisonStrategy.accept(formattedDisksA[i], formattedDisksB[i]);
}
}
/** Compare disks by whatever native geometry the disks have. Fails if geometries do not match. */
public void compareByNativeGeometry(FormattedDisk formattedDiskA, FormattedDisk formattedDiskB) {
DiskGeometry geometryA = formattedDiskA.getDiskGeometry();
DiskGeometry geometryB = formattedDiskB.getDiskGeometry();
if (geometryA != geometryB) {
results.addError("Disks are different geometry (block versus track/sector)");
return;
}
switch (geometryA) {
case BLOCK:
compareByBlockGeometry(formattedDiskA, formattedDiskB);
break;
case TRACK_SECTOR:
compareByTrackSectorGeometry(formattedDiskA, formattedDiskB);
break;
default:
results.addError("Unknown geometry: %s", geometryA);
}
}
/** Compare disks by 512-byte ProDOS/Pascal blocks. */
public void compareByBlockGeometry(FormattedDisk formattedDiskA, FormattedDisk formattedDiskB) {
ImageOrder orderA = formattedDiskA.getImageOrder();
ImageOrder orderB = formattedDiskB.getImageOrder();
if (orderA.getBlocksOnDevice() != orderB.getBlocksOnDevice()) {
results.addError("Different sized disks do not equal. (Blocks: %d <> %d)",
orderA.getBlocksOnDevice(), orderB.getBlocksOnDevice());
return;
}
List<Integer> unequalBlocks = new ArrayList<>();
for (int block=0; block<orderA.getBlocksOnDevice(); block++) {
byte[] blockA = orderA.readBlock(block);
byte[] blockB = orderB.readBlock(block);
if (!Arrays.equals(blockA, blockB)) {
unequalBlocks.add(block);
}
}
for (Range r : Range.from(unequalBlocks)) {
if (r.size() == 1) {
results.addError("Block #%s does not match.", r);
}
else {
results.addError("Blocks #%s do not match.", r);
}
}
}
/** Compare disks by 256-byte DOS sectors. */
public void compareByTrackSectorGeometry(FormattedDisk formattedDiskA, FormattedDisk formattedDiskB) {
ImageOrder orderA = formattedDiskA.getImageOrder();
ImageOrder orderB = formattedDiskB.getImageOrder();
if (orderA.getSectorsPerDisk() != orderB.getSectorsPerDisk()) {
results.addError("Different sized disks do not equal. (Sectors: %d <> %d)",
orderA.getSectorsPerDisk(), orderB.getSectorsPerDisk());
return;
}
for (int track=0; track<orderA.getTracksPerDisk(); track++) {
List<Integer> unequalSectors = new ArrayList<>();
for (int sector=0; sector<orderA.getSectorsPerTrack(); sector++) {
byte[] sectorA = orderA.readSector(track, sector);
byte[] sectorB = orderB.readSector(track, sector);
if (!Arrays.equals(sectorA, sectorB)) {
unequalSectors.add(sector);
}
}
if (!unequalSectors.isEmpty()) {
results.addError("Track %d does not match on sectors %s", track,
Range.from(unequalSectors)
.stream()
.map(Range::toString)
.collect(Collectors.joining(",")));
}
}
}
/** 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<String,List<FileTuple>> filesA = FileStreamer.forDisk(formattedDiskA)
.includeTypeOfFile(TypeOfFile.FILE)
.recursive(true)
.stream()
.collect(Collectors.groupingBy(FileTuple::fullPath));
Map<String,List<FileTuple>> filesB = FileStreamer.forDisk(formattedDiskB)
.includeTypeOfFile(TypeOfFile.FILE)
.recursive(true)
.stream()
.collect(Collectors.groupingBy(FileTuple::fullPath));
Set<String> 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<String> 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<String> pathsInAB = new HashSet<>(filesA.keySet());
pathsInAB.retainAll(filesB.keySet());
for (String path : pathsInAB) {
List<FileTuple> tuplesA = filesA.get(path);
List<FileTuple> 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<String> differences = compare(readerA, readerB);
if (!differences.isEmpty()) {
results.addWarning("Path %s differ: %s", path, String.join(", ", differences));
}
}
} catch (DiskException ex) {
results.addError(ex);
}
}
/** 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) {
try {
Map<String,List<FileTuple>> contentA = FileStreamer.forDisk(formattedDiskA)
.includeTypeOfFile(TypeOfFile.FILE)
.recursive(true)
.stream()
.collect(Collectors.groupingBy(this::contentHash));
Map<String,List<FileTuple>> contentB = FileStreamer.forDisk(formattedDiskB)
.includeTypeOfFile(TypeOfFile.FILE)
.recursive(true)
.stream()
.collect(Collectors.groupingBy(this::contentHash));
Set<String> contentOnlyA = new HashSet<>(contentA.keySet());
contentOnlyA.removeAll(contentB.keySet());
if (!contentOnlyA.isEmpty()) {
Set<String> 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<String> contentOnlyB = new HashSet<>(contentB.keySet());
contentOnlyB.removeAll(contentA.keySet());
if (!contentOnlyB.isEmpty()) {
Set<String> 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<String> contentInAB = new HashSet<>(contentA.keySet());
contentInAB.retainAll(contentB.keySet());
for (String content : contentInAB) {
List<FileTuple> tuplesA = contentA.get(content);
List<FileTuple> 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<String> 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<String> compare(FileEntryReader readerA, FileEntryReader readerB) {
List<String> 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;
public Builder(Disk diskA, Disk diskB) {
diff = new DiskDiff(diskA, diskB);
}
/** Compare disks by whatever native geometry the disks have. Fails if geometries do not match. */
public Builder selectCompareByNativeGeometry() {
diff.diskComparisonStrategy = diff::compareByNativeGeometry;
return this;
}
/** Compare disks by 256-byte DOS sectors. */
public Builder selectCompareByTrackSectorGeometry() {
diff.diskComparisonStrategy = diff::compareByTrackSectorGeometry;
return this;
}
/** Compare disks by 512-byte ProDOS/Pascal blocks. */
public Builder selectCompareByBlockGeometry() {
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();
}
}
}

View File

@ -27,6 +27,7 @@ import java.util.StringTokenizer;
import com.webcodepro.applecommander.storage.DirectoryEntry;
import com.webcodepro.applecommander.storage.DiskFullException;
import com.webcodepro.applecommander.storage.DiskGeometry;
import com.webcodepro.applecommander.storage.FileEntry;
import com.webcodepro.applecommander.storage.FormattedDisk;
import com.webcodepro.applecommander.storage.StorageBundle;
@ -557,4 +558,11 @@ public class CpmFormatDisk extends FormattedDisk {
public DirectoryEntry createDirectory(String name) throws DiskFullException {
throw new UnsupportedOperationException(textBundle.get("DirectoryCreationNotSupported")); //$NON-NLS-1$
}
/**
* Gives an indication on how this disk's geometry should be handled.
*/
public DiskGeometry getDiskGeometry() {
return DiskGeometry.TRACK_SECTOR;
}
}

View File

@ -28,6 +28,7 @@ import com.webcodepro.applecommander.storage.DirectoryEntry;
import com.webcodepro.applecommander.storage.DiskException;
import com.webcodepro.applecommander.storage.DiskCorruptException;
import com.webcodepro.applecommander.storage.DiskFullException;
import com.webcodepro.applecommander.storage.DiskGeometry;
import com.webcodepro.applecommander.storage.FileEntry;
import com.webcodepro.applecommander.storage.FormattedDisk;
import com.webcodepro.applecommander.storage.StorageBundle;
@ -773,4 +774,11 @@ public class DosFormatDisk extends FormattedDisk {
public DirectoryEntry createDirectory(String name) throws DiskFullException {
throw new UnsupportedOperationException(textBundle.get("DirectoryCreationNotSupported")); //$NON-NLS-1$
}
/**
* Gives an indication on how this disk's geometry should be handled.
*/
public DiskGeometry getDiskGeometry() {
return DiskGeometry.TRACK_SECTOR;
}
}

View File

@ -24,6 +24,7 @@ import java.util.List;
import com.webcodepro.applecommander.storage.DirectoryEntry;
import com.webcodepro.applecommander.storage.DiskFullException;
import com.webcodepro.applecommander.storage.DiskGeometry;
import com.webcodepro.applecommander.storage.FileEntry;
import com.webcodepro.applecommander.storage.FormattedDisk;
import com.webcodepro.applecommander.storage.StorageBundle;
@ -699,4 +700,11 @@ public class GutenbergFormatDisk extends FormattedDisk {
public DirectoryEntry createDirectory(String name) throws DiskFullException {
throw new UnsupportedOperationException(textBundle.get("DirectoryCreationNotSupported")); //$NON-NLS-1$
}
/**
* Gives an indication on how this disk's geometry should be handled.
*/
public DiskGeometry getDiskGeometry() {
return DiskGeometry.TRACK_SECTOR;
}
}

View File

@ -24,6 +24,7 @@ import java.util.List;
import com.webcodepro.applecommander.storage.DirectoryEntry;
import com.webcodepro.applecommander.storage.DiskFullException;
import com.webcodepro.applecommander.storage.DiskGeometry;
import com.webcodepro.applecommander.storage.FileEntry;
import com.webcodepro.applecommander.storage.FormattedDisk;
import com.webcodepro.applecommander.storage.StorageBundle;
@ -537,4 +538,11 @@ public class NakedosFormatDisk extends FormattedDisk {
public DirectoryEntry createDirectory(String name) throws DiskFullException {
throw new UnsupportedOperationException(textBundle.get("DirectoryCreationNotSupported")); //$NON-NLS-1$
}
/**
* Gives an indication on how this disk's geometry should be handled.
*/
public DiskGeometry getDiskGeometry() {
return DiskGeometry.TRACK_SECTOR;
}
}

View File

@ -28,6 +28,7 @@ import java.util.List;
import com.webcodepro.applecommander.storage.DirectoryEntry;
import com.webcodepro.applecommander.storage.DiskFullException;
import com.webcodepro.applecommander.storage.DiskGeometry;
import com.webcodepro.applecommander.storage.FileEntry;
import com.webcodepro.applecommander.storage.FormattedDisk;
import com.webcodepro.applecommander.storage.StorageBundle;
@ -667,4 +668,11 @@ public class PascalFormatDisk extends FormattedDisk {
public DirectoryEntry createDirectory(String name) throws DiskFullException {
throw new UnsupportedOperationException(textBundle.get("DirectoryCreationNotSupported")); //$NON-NLS-1$
}
/**
* Gives an indication on how this disk's geometry should be handled.
*/
public DiskGeometry getDiskGeometry() {
return DiskGeometry.BLOCK;
}
}

View File

@ -32,6 +32,7 @@ import com.webcodepro.applecommander.storage.DirectoryEntry;
import com.webcodepro.applecommander.storage.DiskException;
import com.webcodepro.applecommander.storage.DiskCorruptException;
import com.webcodepro.applecommander.storage.DiskFullException;
import com.webcodepro.applecommander.storage.DiskGeometry;
import com.webcodepro.applecommander.storage.FileEntry;
import com.webcodepro.applecommander.storage.FormattedDisk;
import com.webcodepro.applecommander.storage.StorageBundle;
@ -1450,4 +1451,11 @@ public class ProdosFormatDisk extends FormattedDisk {
throw new DiskFullException(textBundle.get("ProdosFormatDisk.UnableToAllocateFileEntry"), this.getFilename());
}
}
/**
* Gives an indication on how this disk's geometry should be handled.
*/
public DiskGeometry getDiskGeometry() {
return DiskGeometry.BLOCK;
}
}

View File

@ -25,6 +25,7 @@ import java.util.List;
import com.webcodepro.applecommander.storage.DirectoryEntry;
import com.webcodepro.applecommander.storage.DiskFullException;
import com.webcodepro.applecommander.storage.DiskGeometry;
import com.webcodepro.applecommander.storage.FileEntry;
import com.webcodepro.applecommander.storage.FormattedDisk;
import com.webcodepro.applecommander.storage.StorageBundle;
@ -518,4 +519,11 @@ public class RdosFormatDisk extends FormattedDisk {
public DirectoryEntry createDirectory(String name) throws DiskFullException {
throw new UnsupportedOperationException(textBundle.get("DirectoryCreationNotSupported")); //$NON-NLS-1$
}
/**
* Gives an indication on how this disk's geometry should be handled.
*/
public DiskGeometry getDiskGeometry() {
return DiskGeometry.TRACK_SECTOR;
}
}

View File

@ -22,12 +22,10 @@ package com.webcodepro.applecommander.util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import com.webcodepro.applecommander.storage.FormattedDisk;
import com.webcodepro.applecommander.storage.physical.ImageOrder;
/**
@ -616,28 +614,6 @@ public class AppleUtil {
return sourceOrder.getSectorsPerDisk() == targetOrder.getSectorsPerDisk();
}
/**
* Compare two disks by track and sector.
*/
public static boolean disksEqualByTrackAndSector(FormattedDisk sourceDisk, FormattedDisk targetDisk) {
ImageOrder sourceOrder = sourceDisk.getImageOrder();
ImageOrder targetOrder = targetDisk.getImageOrder();
if (!sameSectorsPerDisk(sourceOrder, targetOrder)) {
throw new IllegalArgumentException(textBundle.
get("AppleUtil.CannotCompareDisks")); //$NON-NLS-1$
}
for (int track = 0; track < sourceOrder.getTracksPerDisk(); track++) {
for (int sector = 0; sector < sourceOrder.getSectorsPerTrack(); sector++) {
byte[] sourceData = sourceOrder.readSector(track, sector);
byte[] targetData = targetOrder.readSector(track, sector);
if (!Arrays.equals(sourceData, targetData)) {
return false;
}
}
}
return true;
}
/**
* Change ImageOrder from source order to target order by copying block by block.
*/
@ -658,24 +634,4 @@ public class AppleUtil {
protected static boolean sameBlocksPerDisk(ImageOrder sourceOrder, ImageOrder targetOrder) {
return sourceOrder.getBlocksOnDevice() == targetOrder.getBlocksOnDevice();
}
/**
* Compare two disks block by block.
*/
public static boolean disksEqualByBlock(FormattedDisk sourceDisk, FormattedDisk targetDisk) {
ImageOrder sourceOrder = sourceDisk.getImageOrder();
ImageOrder targetOrder = targetDisk.getImageOrder();
if (!sameBlocksPerDisk(sourceOrder, targetOrder)) {
throw new IllegalArgumentException(textBundle.
get("AppleUtil.CannotCompareDisks")); //$NON-NLS-1$
}
for (int block = 0; block < sourceOrder.getBlocksOnDevice(); block++) {
byte[] sourceData = sourceOrder.readBlock(block);
byte[] targetData = targetOrder.readBlock(block);
if (!Arrays.equals(sourceData, targetData)) {
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,89 @@
/*
* 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 java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Represents a range of numbers with helper methods to put them together.
*/
public class Range {
private int first;
private int last;
public Range(int first, int last) {
if (first < last) {
this.first = first;
this.last = last;
}
else {
this.first = last;
this.last = first;
}
}
public int getFirst() {
return first;
}
public int getLast() {
return last;
}
public int size() {
return last - first + 1;
}
@Override
public String toString() {
if (first == last) {
return String.format("%d", first);
}
else {
return String.format("%d-%d", first, last);
}
}
public static List<Range> from(List<Integer> numbers) {
List<Range> 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;
}
}

View File

@ -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<PathMatcher> 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) {

View File

@ -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<String> 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<String>(), (DirectoryEntry)disk, null);

View File

@ -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;

View File

@ -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<Date> 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);

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -289,10 +289,10 @@ CompareDisksStartPane.DiskNLabel=Please select disk image \#{0}:
# CompareDisksResultsPane
CompareDisksResultsPane.RestartText=If you wish to compare more disks, click back and start again.
CompareDisksResultsPane.UnableToLoadDiskN=Unable to load disk \#{0}: {1}\n
CompareDisksResultsPane.DifferentSizeError=The two disks are of differing formats - unable to compare.\n
CompareDisksResultsPane.DataDiffersMessage=The two disks do not contain the same data.\n
CompareDisksResultsPane.DifferentDataFormatError=The two disks are not the same data format.\n
CompareDisksResultsPane.UnableToLoadDiskN=Unable to load disk \#{0}: {1}
CompareDisksResultsPane.DifferentSizeError=The two disks are of differing formats - unable to compare.
CompareDisksResultsPane.DataDiffersMessage=The two disks do not contain the same data.
CompareDisksResultsPane.DifferentDataFormatError=The two disks are not the same data format.
CompareDisksResultsPane.DisksMatch=The disk images match.
# GraphicsFilterAdapter

View File

@ -28,6 +28,8 @@ import org.junit.Test;
import com.webcodepro.applecommander.storage.Disk;
import com.webcodepro.applecommander.storage.DiskFullException;
import com.webcodepro.applecommander.storage.FileEntry;
import com.webcodepro.applecommander.storage.compare.ComparisonResult;
import com.webcodepro.applecommander.storage.compare.DiskDiff;
import com.webcodepro.applecommander.storage.os.dos33.DosFormatDisk;
import com.webcodepro.applecommander.storage.os.prodos.ProdosFormatDisk;
import com.webcodepro.applecommander.storage.physical.ByteArrayImageLayout;
@ -92,7 +94,9 @@ public class AppleUtilTest {
AppleUtil.changeImageOrderByTrackAndSector(dosDiskDosOrder.getImageOrder(),
dosDiskNibbleOrder.getImageOrder());
// Confirm that these disks are identical:
assertTrue(AppleUtil.disksEqualByTrackAndSector(dosDiskDosOrder, dosDiskNibbleOrder));
ComparisonResult result = DiskDiff.create(dosDiskDosOrder, dosDiskDosOrder)
.selectCompareByTrackSectorGeometry().compare();
assertEquals("Expected disks to have no differences", 0, result.getDifferenceCount());
}
@Test
@ -112,7 +116,9 @@ public class AppleUtilTest {
AppleUtil.changeImageOrderByBlock(prodosDiskDosOrder.getImageOrder(),
prodosDiskNibbleOrder.getImageOrder());
// Confirm that these disks are identical:
assertTrue(AppleUtil.disksEqualByBlock(prodosDiskDosOrder, prodosDiskNibbleOrder));
ComparisonResult result = DiskDiff.create(prodosDiskDosOrder, prodosDiskNibbleOrder)
.selectCompareByBlockGeometry().compare();
assertEquals("Expected disks to have no differences", 0, result.getDifferenceCount());
}
@Test

View File

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

View File

@ -19,16 +19,19 @@
*/
package com.webcodepro.applecommander.ui.swt.wizard.comparedisks;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import com.webcodepro.applecommander.storage.Disk;
import com.webcodepro.applecommander.storage.FormattedDisk;
import com.webcodepro.applecommander.storage.compare.ComparisonResult;
import com.webcodepro.applecommander.storage.compare.DiskDiff;
import com.webcodepro.applecommander.ui.UiBundle;
import com.webcodepro.applecommander.ui.swt.wizard.WizardPane;
import com.webcodepro.applecommander.util.AppleUtil;
import com.webcodepro.applecommander.util.TextBundle;
/**
@ -75,6 +78,8 @@ public class CompareDisksResultsPane extends WizardPane {
label = new Label(control, SWT.WRAP);
label.setText(textBundle.get("CompareDisksResultsPane.RestartText")); //$NON-NLS-1$
parent.pack();
}
/**
* Get the next pane. A null return indicates the end of the wizard.
@ -93,49 +98,47 @@ public class CompareDisksResultsPane extends WizardPane {
}
protected String compareDisks() {
StringBuffer errorMessages = new StringBuffer();
FormattedDisk[] disk1 = null;
List<String> 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$
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");
}
} else if (!disk1TSformat && !disk2TSformat) {
if (!AppleUtil.disksEqualByBlock(disk1[0], disk2[0])) {
errorMessages.append(textBundle.get(
"CompareDisksResultsPane.DataDiffersMessage")); //$NON-NLS-1$
ComparisonResult result = builder.compare();
errorMessages.addAll(result.getLimitedMessages(wizard.getMessageLimit()));
}
} else {
errorMessages.append(textBundle.get(
"CompareDisksResultsPane.DifferentDataFormatError")); //$NON-NLS-1$
}
}
}
if (errorMessages.length() == 0) {
if (errorMessages.size() == 0) {
return textBundle.get("CompareDisksResultsPane.DisksMatch"); //$NON-NLS-1$
}
return errorMessages.toString();
return String.join("\n", errorMessages);
}
}

View File

@ -24,10 +24,10 @@ 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;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
@ -50,6 +50,8 @@ public class CompareDisksStartPane extends WizardPane {
private CompareDisksWizard wizard;
private Text diskname1Text;
private Text diskname2Text;
private Combo comparisonStrategyCombo;
private Text limitText;
/**
* Constructor for CompareDisksStartPane.
*/
@ -75,6 +77,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 +87,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 +115,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 +135,32 @@ 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:");
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 +193,12 @@ public class CompareDisksStartPane extends WizardPane {
return textBundle.format("CompareDisksStartPane.DiskNLabel", //$NON-NLS-1$
diskNumber);
}
protected void limitTextModifyListener(ModifyEvent event) {
try {
getWizard().setMessageLimit(Integer.parseInt(limitText.getText()));
} catch (NumberFormatException e) {
limitText.setText(Integer.toString(getWizard().getMessageLimit()));
}
}
}

View File

@ -34,6 +34,8 @@ 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.
*/
@ -53,6 +55,12 @@ public class CompareDisksWizard extends Wizard {
}
public String getDiskname2() {
return diskname2;
}
public int getComparisonStrategy() {
return comparisonStrategy;
}
public int getMessageLimit() {
return messageLimit;
}
public void setDiskname1(String string) {
diskname1 = string;
@ -60,5 +68,10 @@ 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;
}
}