Adding filename disk comparison strategy.

This commit is contained in:
Rob Greene 2022-03-17 21:19:43 -05:00
parent fdb4a6d566
commit e6cbda9908
17 changed files with 200 additions and 14 deletions

View File

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

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

@ -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<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);
}
}
public void compareByFileContent(FormattedDisk formattedDiskA, FormattedDisk formattedDiskB) {
// TODO
}
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;
@ -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();

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

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

View File

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

View File

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