mirror of
https://github.com/AppleCommander/AppleCommander.git
synced 2026-10-24 23:02:27 +00:00
Adding disk optimization. Pascal only.
This commit is contained in:
1 parent
811128623a
commit
8b8749e57c
9 files changed
+316
-2
No files matched your search
@@ -57,6 +57,7 @@ import java.util.logging.Logger;
|
||||
ListCommand.class,
|
||||
LockCommand.class,
|
||||
MkdirCommand.class,
|
||||
OptimizeCommand.class,
|
||||
ProofCommand.class,
|
||||
ReadCommand.class,
|
||||
RenameFileCommand.class,
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* AppleCommander - An Apple ][ image utility.
|
||||
* Copyright (C) 2026 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 com.webcodepro.applecommander.storage.FormattedDisk;
|
||||
import io.github.applecommander.acx.base.ReadWriteDiskCommandOptions;
|
||||
import org.applecommander.os.DiskOptimizer;
|
||||
import picocli.CommandLine.Command;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Command(name = "optimize", aliases = { "krunch" }, description = "Optimize disk image.")
|
||||
public class OptimizeCommand extends ReadWriteDiskCommandOptions {
|
||||
@Override
|
||||
public int handleCommand() throws Exception {
|
||||
int completed = 0;
|
||||
int skipped = 0;
|
||||
for (FormattedDisk disk : selectedDisks()) {
|
||||
Optional<DiskOptimizer> opt = disk.get(DiskOptimizer.class);
|
||||
if (opt.isEmpty()) {
|
||||
System.out.printf("Optimization not supported for disk %s of type %s.\n", disk.getFilename(), disk.getFormat());
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
DiskOptimizer optimizer = opt.get();
|
||||
if (!optimizer.canOptimizeDisk()) {
|
||||
System.out.printf("Disk %s does not need optimization.\n", disk.getFilename());
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
optimizer.optimizeDisk();
|
||||
completed++;
|
||||
System.out.printf("Disk %s has been optimized.\n", disk.getFilename());
|
||||
}
|
||||
System.out.printf("%d disks were optimized, %d disks were skipped.\n", completed, skipped);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* AppleCommander - An Apple ][ image utility.
|
||||
* Copyright (C) 2026 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.os.pascal;
|
||||
|
||||
import com.webcodepro.applecommander.storage.FileEntry;
|
||||
import org.applecommander.os.DiskOptimizer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A simple Pascal disk optimizer. This should emulate a "krunch" to the front of the disk.
|
||||
*/
|
||||
public class PascalDiskOptimizer implements DiskOptimizer {
|
||||
private PascalFormatDisk disk;
|
||||
|
||||
public PascalDiskOptimizer(PascalFormatDisk disk) {
|
||||
this.disk = disk;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canOptimizeDisk() {
|
||||
int nextBlock = 6;
|
||||
for (PascalFileEntry fileEntry : getSortedFiles()) {
|
||||
if (fileEntry.getFirstBlock() != nextBlock) {
|
||||
return true;
|
||||
}
|
||||
nextBlock = fileEntry.getLastBlock();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void optimizeDisk() {
|
||||
int nextBlock = 6;
|
||||
for (PascalFileEntry fileEntry : getSortedFiles()) {
|
||||
if (fileEntry.getFirstBlock() != nextBlock) {
|
||||
int offset = fileEntry.getFirstBlock() - nextBlock;
|
||||
// We move both pointers where we think they should be. And then save the file to move the data.
|
||||
byte[] fileData = fileEntry.getFileData();
|
||||
fileEntry.setFirstBlock(fileEntry.getFirstBlock() - offset);
|
||||
fileEntry.setLastBlock(fileEntry.getLastBlock() - offset);
|
||||
try {
|
||||
fileEntry.setFileData(fileData);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Unexpected error", e);
|
||||
}
|
||||
}
|
||||
nextBlock = fileEntry.getLastBlock();
|
||||
}
|
||||
}
|
||||
|
||||
public List<PascalFileEntry> getSortedFiles() {
|
||||
List<PascalFileEntry> sortedFiles = new ArrayList<>();
|
||||
for (FileEntry fileEntry : disk.getFiles()) {
|
||||
sortedFiles.add((PascalFileEntry) fileEntry);
|
||||
}
|
||||
sortedFiles.sort(Comparator.comparingInt(PascalFileEntry::getFirstBlock));
|
||||
return sortedFiles;
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -119,6 +119,7 @@ public class PascalFormatDisk extends FormattedDisk {
|
||||
}
|
||||
|
||||
private final BlockDevice device;
|
||||
private final PascalDiskOptimizer optimizer;
|
||||
|
||||
/**
|
||||
* Constructor for PascalFormatDisk.
|
||||
@@ -126,6 +127,7 @@ public class PascalFormatDisk extends FormattedDisk {
|
||||
public PascalFormatDisk(String filename, BlockDevice device) {
|
||||
super(filename, device.get(Source.class).orElseThrow());
|
||||
this.device = device;
|
||||
this.optimizer = new PascalDiskOptimizer(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -147,7 +149,7 @@ public class PascalFormatDisk extends FormattedDisk {
|
||||
|
||||
@Override
|
||||
public <T> Optional<T> get(Class<T> iface) {
|
||||
return Container.get(iface, device);
|
||||
return Container.get(iface, device, optimizer);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* AppleCommander - An Apple ][ image utility.
|
||||
* Copyright (C) 2026 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 org.applecommander.os;
|
||||
|
||||
/**
|
||||
* An optional disk optimizer interface.
|
||||
*
|
||||
* @see org.applecommander.util.Container#get(Class)
|
||||
*/
|
||||
public interface DiskOptimizer {
|
||||
/**
|
||||
* Indicates if this disk can be optimized.
|
||||
* For instance, a Pascal disk can be "krunched" to pack the files together but it may not need it.
|
||||
*/
|
||||
boolean canOptimizeDisk();
|
||||
|
||||
/**
|
||||
* Perform disk optimization.
|
||||
*/
|
||||
void optimizeDisk();
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* AppleCommander - An Apple ][ image utility.
|
||||
* Copyright (C) 2002-2026 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.os.pascal;
|
||||
|
||||
import com.webcodepro.applecommander.storage.DiskConstants;
|
||||
import com.webcodepro.applecommander.storage.DiskFullException;
|
||||
import com.webcodepro.applecommander.storage.FileEntry;
|
||||
import org.applecommander.device.BlockDevice;
|
||||
import org.applecommander.device.ProdosOrderedBlockDevice;
|
||||
import org.applecommander.os.DiskOptimizer;
|
||||
import org.applecommander.source.DataBufferSource;
|
||||
import org.applecommander.source.Source;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class PascalDiskOptimizerTest {
|
||||
@Test
|
||||
public void testKrunch() throws DiskFullException {
|
||||
Source source = DataBufferSource.create(DiskConstants.APPLE_140KB_DISK, "new-disk").get();
|
||||
BlockDevice blockDevice = new ProdosOrderedBlockDevice(source, BlockDevice.STANDARD_BLOCK_SIZE);
|
||||
PascalFormatDisk[] disks = PascalFormatDisk.create("deleteme.po", "TEST", blockDevice);
|
||||
PascalFormatDisk disk = disks[0];
|
||||
|
||||
// Strategy:
|
||||
// 1. Create a set of files. At least one single block file.
|
||||
// 2. Delete the first file (opening up a gap).
|
||||
// 3. "Krunch"
|
||||
// 4. Check expected block numbers.
|
||||
|
||||
PascalFileEntry file0 = createFileData(disk, "file0", 2000); // 4 blocks (gets deleted)
|
||||
PascalFileEntry file1 = createFileData(disk, "file1", 2048); // 4 blocks
|
||||
PascalFileEntry file2 = createFileData(disk, "file2", 4000); // 8 blocks
|
||||
PascalFileEntry file3 = createFileData(disk, "file3", 500); // 1 block
|
||||
PascalFileEntry file4 = createFileData(disk, "file4", 2000); // 4 blocks
|
||||
|
||||
// Need to capture the random data BEFORE optimization occurs
|
||||
final byte[][] expectedFileData = { file1.getFileData(), file2.getFileData(), file3.getFileData(), file4.getFileData() };
|
||||
|
||||
DiskOptimizer optimizer = disk.get(DiskOptimizer.class).orElseThrow();
|
||||
assertFalse(optimizer.canOptimizeDisk());
|
||||
|
||||
file0.delete();
|
||||
assertTrue(optimizer.canOptimizeDisk());
|
||||
|
||||
optimizer.optimizeDisk();
|
||||
|
||||
// Some of the file data is cached, so we do a fresh pull from the image
|
||||
final int[] expectedFirstBlock = { 6, 10, 18, 19 };
|
||||
final int[] expectedLastBlock = { 10, 18, 19, 23 };
|
||||
List<FileEntry> fileEntries = disk.getFiles();
|
||||
assertEquals(4, fileEntries.size());
|
||||
for (int i=0; i<fileEntries.size(); i++) {
|
||||
PascalFileEntry fileEntry = (PascalFileEntry) fileEntries.get(i);
|
||||
assertEquals(expectedFirstBlock[i], fileEntry.getFirstBlock());
|
||||
assertEquals(expectedLastBlock[i], fileEntry.getLastBlock());
|
||||
assertArrayEquals(expectedFileData[i], fileEntry.getFileData());
|
||||
}
|
||||
}
|
||||
|
||||
public PascalFileEntry createFileData(PascalFormatDisk disk, String filename, int size) throws DiskFullException {
|
||||
byte[] fileData = new byte[size];
|
||||
new Random().nextBytes(fileData);
|
||||
|
||||
PascalFileEntry fileEntry = disk.createFile();
|
||||
fileEntry.setFilename(filename);
|
||||
fileEntry.setFiletype("data");
|
||||
fileEntry.setFileData(fileData);
|
||||
return fileEntry;
|
||||
}
|
||||
}
|
||||
+47
-1
@@ -49,6 +49,7 @@ import org.applecommander.device.Device;
|
||||
import org.applecommander.hint.Hint;
|
||||
import org.applecommander.image.NibbleImage;
|
||||
import org.applecommander.os.DiskCheck;
|
||||
import org.applecommander.os.DiskOptimizer;
|
||||
import org.applecommander.source.DataBufferSource;
|
||||
import org.applecommander.source.Source;
|
||||
import org.applecommander.source.Sources;
|
||||
@@ -112,6 +113,7 @@ public class DiskExplorerTab {
|
||||
private ToolItem saveAsToolItem;
|
||||
private ToolItem changeOrderToolItem;
|
||||
private ToolItem checkDiskToolItem;
|
||||
private ToolItem optimizeDiskToolItem;
|
||||
private Menu changeImageOrderMenu;
|
||||
|
||||
private final UserPreferences userPreferences = UserPreferences.getInstance();
|
||||
@@ -1342,7 +1344,25 @@ public class DiskExplorerTab {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
optimizeDiskToolItem = new ToolItem(toolBar, SWT.PUSH);
|
||||
optimizeDiskToolItem.setImage(imageManager.get(ImageManager.ICON_DISK_OPTIMIZE));
|
||||
optimizeDiskToolItem.setText("Optimize");
|
||||
optimizeDiskToolItem.setToolTipText("Optimize disk.");
|
||||
optimizeDiskToolItem.setEnabled(false);
|
||||
for (FormattedDisk disk : disks) {
|
||||
if (disk.get(DiskOptimizer.class).isPresent()) optimizeDiskToolItem.setEnabled(true);
|
||||
}
|
||||
optimizeDiskToolItem.addSelectionListener(new SelectionAdapter () {
|
||||
public void widgetSelected(SelectionEvent e) {
|
||||
try {
|
||||
optimizeDisk();
|
||||
} catch (Exception ex) {
|
||||
showError(ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
new ToolItem(toolBar, SWT.SEPARATOR);
|
||||
|
||||
saveToolItem = new ToolItem(toolBar, SWT.PUSH);
|
||||
@@ -2198,6 +2218,32 @@ public class DiskExplorerTab {
|
||||
dialog.open();
|
||||
}
|
||||
|
||||
protected void optimizeDisk() {
|
||||
int completed = 0;
|
||||
int didntNeed = 0;
|
||||
int skipped = 0;
|
||||
for (FormattedDisk disk : disks) {
|
||||
Optional<DiskOptimizer> opt = disk.get(DiskOptimizer.class);
|
||||
if (opt.isEmpty()) {
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
DiskOptimizer optimizer = opt.get();
|
||||
if (!optimizer.canOptimizeDisk()) {
|
||||
didntNeed++;
|
||||
continue;
|
||||
}
|
||||
optimizer.optimizeDisk();
|
||||
completed++;
|
||||
}
|
||||
|
||||
MessageBox box = new MessageBox(shell, SWT.ICON_INFORMATION | SWT.CLOSE);
|
||||
box.setText("Optimization results");
|
||||
box.setMessage(String.format("%d disks were optimized, %d disks didn't need optimization, and %d disks were skipped.",
|
||||
completed, didntNeed, skipped));
|
||||
box.open();
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the popup menu for the export button on the toolbar.
|
||||
*/
|
||||
|
||||
+2
@@ -72,6 +72,7 @@ public class ImageManager {
|
||||
public static final String ICON_HEALTH_CHECK = "health-check.png";
|
||||
public static final String ICON_PLUS = "plus.png";
|
||||
public static final String ICON_MINUS = "minus.png";
|
||||
public static final String ICON_DISK_OPTIMIZE = "disk-optimize.png";
|
||||
|
||||
public static final String LOGO_EXPORT_WIZARD = "ExportWizardLogo.jpg";
|
||||
public static final String LOGO_APPLECOMMANDER = "AppleCommanderLogo.jpg";
|
||||
@@ -98,6 +99,7 @@ public class ImageManager {
|
||||
ICON_CHANGE_IMAGE_ORDER, ICON_SHAPE_TABLE,
|
||||
ICON_SETTINGS, ICON_HEALTH_CHECK,
|
||||
ICON_PLUS, ICON_MINUS,
|
||||
ICON_DISK_OPTIMIZE,
|
||||
// Logos:
|
||||
LOGO_EXPORT_WIZARD, LOGO_APPLECOMMANDER,
|
||||
LOGO_DISK_IMAGE_WIZARD, LOGO_IMPORT_WIZARD,
|
||||
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 550 B |
Reference in new issue
Block a user