diff --git a/lib/ac-api/src/main/java/org/applecommander/filestore/FileStoreFactory.java b/lib/ac-api/src/main/java/org/applecommander/filestore/FileStoreFactory.java index 12541f39..3d4489be 100644 --- a/lib/ac-api/src/main/java/org/applecommander/filestore/FileStoreFactory.java +++ b/lib/ac-api/src/main/java/org/applecommander/filestore/FileStoreFactory.java @@ -20,6 +20,7 @@ package org.applecommander.filestore; import com.webcodepro.applecommander.storage.DiskConstants; +import org.applecommander.device.*; import org.applecommander.device.nibble.NibbleTrackReaderWriter; import org.applecommander.hint.Hint; import org.applecommander.image.NibbleImage; @@ -28,12 +29,33 @@ import org.applecommander.source.Source; import java.util.ArrayList; import java.util.List; +import java.util.Optional; -/** - * The FileStoreFactory inspects a given Source to see if it matches any known file store(s). - * Invoke via {@link FileStores#inspect(Source)} which will return a Context. The Context _can be empty_. - * Note that most logic is deferred to the "old" AppleCommander APIs for the time being. - */ +/// The FileStoreFactory inspects a given Source to see if it matches any known file store(s). +/// Invoke via [FileStores#inspect(Source)] which will return a Context. The Context _can be empty_. +/// If this is the case, devices can be created via the [FileStoreFactory.Context#blockDevice()] and +/// [Context#trackSectorDevice()] builders. +/// +/// The builders follow the observation about what each filesystem currently uses: +/// +/// | OS | 13-sector | 16-sector | 800K | HDV | +/// | :--- | :--- | :--- | :--- | :--- | +/// | CP/M | - | CP/M | - | - | +/// | DOS | Physical | DOS | Pascal | - | +/// | GameDOS | - | DOS | - | - | +/// | Gutenberg | - | DOS | - | - | +/// | NakedOS | - | Physical | - | - | +/// | Pascal | - | Pascal | Pascal | Pascal | +/// | RDOS | Physical | DOS | - | - | +/// +/// * 13-sector disks are only track/sector devices. (And RDOS has a unique mapping to 256 byte blocks). +/// * 16-sector disks have a variety of sector mappings. It can also double as a "standard" 512 byte block, +/// as well as a unique CP/M 1024 block device. +/// * 800K disks are only standard 512 byte block (Prodos or Pascal). OzDOS and UniDOS do have unique +/// mappings to track and sector, but logically, they are block devices. +/// * Anything else (larger than 800K) are "hard-disk" images and only blocks. +/// +/// Note that much of this logic is deferred to the "old" AppleCommander APIs for the time being. public interface FileStoreFactory { void inspect(Context ctx); @@ -55,5 +77,142 @@ public interface FileStoreFactory { nibbleTrackReaderWriter = null; } } + + public BlockDeviceBuilder blockDevice() { + return new BlockDeviceBuilder(this); + } + public static class BlockDeviceBuilder { + private final Context ctx; + private final List devices = new ArrayList<>(); + private BlockDeviceBuilder(Context ctx) { + this.ctx = ctx; + } + public BlockDeviceBuilder include16Sector(Hint hint) { + if (ctx.nibbleTrackReaderWriter != null) { + Optional nibble = TrackSectorNibbleDevice.identify(ctx.nibbleTrackReaderWriter); + if (nibble.isPresent()) { + TrackSectorDevice converted = switch (hint) { + case DOS_SECTOR_ORDER -> SkewedTrackSectorDevice.physicalToDosSkew(nibble.get()); + case PRODOS_BLOCK_ORDER -> SkewedTrackSectorDevice.physicalToPascalSkew(nibble.get()); + case NIBBLE_SECTOR_ORDER -> nibble.get(); + default -> throw new RuntimeException("wrong hint type: " + hint); + }; + devices.add(new TrackSectorToBlockAdapter(converted, TrackSectorToBlockAdapter.BlockStyle.PRODOS)); + } + } + else if (ctx.source.isApproxBetween(DiskConstants.APPLE_140KB_DISK, DiskConstants.APPLE_160KB_DISK)) { + if (ctx.source.is(Hint.DOS_SECTOR_ORDER) || ctx.source.extensionLike("do")) { + TrackSectorDevice doDevice = new DosOrderedTrackSectorDevice(ctx.source, Hint.DOS_SECTOR_ORDER); + TrackSectorDevice poDevice = SkewedTrackSectorDevice.dosToPascalSkew(doDevice); + devices.add(new TrackSectorToBlockAdapter(poDevice, TrackSectorToBlockAdapter.BlockStyle.PRODOS)); + } + else if (ctx.source.is(Hint.PRODOS_BLOCK_ORDER) || ctx.source.extensionLike("po")) { + devices.add(new ProdosOrderedBlockDevice(ctx.source, BlockDevice.STANDARD_BLOCK_SIZE)); + } + else { + devices.add(new ProdosOrderedBlockDevice(ctx.source, BlockDevice.STANDARD_BLOCK_SIZE)); + TrackSectorDevice doDevice = new DosOrderedTrackSectorDevice(ctx.source, Hint.DOS_SECTOR_ORDER); + TrackSectorDevice poDevice = SkewedTrackSectorDevice.dosToPascalSkew(doDevice); + devices.add(new TrackSectorToBlockAdapter(poDevice, TrackSectorToBlockAdapter.BlockStyle.PRODOS)); + } + } + return this; + } + public BlockDeviceBuilder include800K() { + if (ctx.source.isApproxEQ(DiskConstants.APPLE_800KB_DISK)) { + devices.add(new ProdosOrderedBlockDevice(ctx.source, BlockDevice.STANDARD_BLOCK_SIZE)); + } + return this; + } + public BlockDeviceBuilder includeHDV() { + // Anything not a floppy is included -- but if we have a device, assume it was picked up elsewhere... + if (ctx.source.getSize() > DiskConstants.APPLE_140KB_NIBBLE_DISK && devices.isEmpty()) { + devices.add(new ProdosOrderedBlockDevice(ctx.source, BlockDevice.STANDARD_BLOCK_SIZE)); + } + return this; + } + public List get() { + return devices; + } + } + + public TrackSectorDeviceBuilder trackSectorDevice() { + return new TrackSectorDeviceBuilder(this); + } + public static class TrackSectorDeviceBuilder { + private final Context ctx; + private final List devices = new ArrayList<>(); + private TrackSectorDeviceBuilder(Context ctx) { + this.ctx = ctx; + } + public TrackSectorDeviceBuilder include13Sector() { + if (ctx.nibbleTrackReaderWriter != null) { + Optional nibble = TrackSectorNibbleDevice.identify(ctx.nibbleTrackReaderWriter); + nibble.ifPresent(device -> { + if (device.getGeometry().sectorsPerTrack() == 13) { + devices.add(device); + } + }); + } + else if (ctx.source.isApproxEQ(DiskConstants.APPLE_13SECTOR_DISK)) { + devices.add(new DosOrderedTrackSectorDevice(ctx.source)); + } + return this; + } + public TrackSectorDeviceBuilder include16Sector(Hint hint) { + assert hint == Hint.DOS_SECTOR_ORDER || hint == Hint.NIBBLE_SECTOR_ORDER; + if (ctx.nibbleTrackReaderWriter != null) { + Optional nibble = TrackSectorNibbleDevice.identify(ctx.nibbleTrackReaderWriter); + nibble.ifPresent(device -> { + if (device.getGeometry().sectorsPerTrack() == 16) { + TrackSectorDevice converted = switch (hint) { + case DOS_SECTOR_ORDER -> SkewedTrackSectorDevice.physicalToDosSkew(nibble.get()); + case NIBBLE_SECTOR_ORDER -> nibble.get(); + default -> throw new RuntimeException("wrong hint type: " + hint); + }; + devices.add(converted); + } + }); + } + else if (ctx.source.isApproxBetween(DiskConstants.APPLE_140KB_DISK, DiskConstants.APPLE_160KB_DISK)) { + TrackSectorDevice doDevice = null; + TrackSectorDevice poDevice = null; + if (ctx.source.is(Hint.DOS_SECTOR_ORDER) || ctx.source.extensionLike("do")) { + doDevice = new DosOrderedTrackSectorDevice(ctx.source, Hint.DOS_SECTOR_ORDER); + } + else if (ctx.source.is(Hint.PRODOS_BLOCK_ORDER) || ctx.source.extensionLike("po")) { + BlockDevice blockDevice = new ProdosOrderedBlockDevice(ctx.source, BlockDevice.STANDARD_BLOCK_SIZE); + poDevice = new BlockToTrackSectorAdapter(blockDevice, new ProdosBlockToTrackSectorAdapterStrategy()); + } + else { + doDevice = new DosOrderedTrackSectorDevice(ctx.source, Hint.DOS_SECTOR_ORDER); + BlockDevice blockDevice = new ProdosOrderedBlockDevice(ctx.source, BlockDevice.STANDARD_BLOCK_SIZE); + poDevice = new BlockToTrackSectorAdapter(blockDevice, new ProdosBlockToTrackSectorAdapterStrategy()); + } + switch (hint) { + case DOS_SECTOR_ORDER -> { + if (doDevice != null) { + devices.add(doDevice); + } + if (poDevice != null) { + devices.add(poDevice); + } + } + case NIBBLE_SECTOR_ORDER -> { + if (doDevice != null) { + devices.add(SkewedTrackSectorDevice.dosToPhysicalSkew(doDevice)); + } + if (poDevice != null) { + devices.add(SkewedTrackSectorDevice.dosToPhysicalSkew(poDevice)); + } + } + } + } + return this; + } + public List get() { + return devices; + } + } } } \ No newline at end of file