From f6d083d6bf9255b7c5972ca4014bec87a26bbff3 Mon Sep 17 00:00:00 2001 From: a2geek Date: Mon, 5 Oct 2026 13:40:24 -0500 Subject: [PATCH] Updates sources to support 'can(Capability.SAVE_SOURCE)' as appropriate and added/implemented 'save()'. --- .../storage/FileEntrySource.java | 15 ++- .../archive/shrinkit/ShrinkitFileStore.java | 2 + .../applecommander/image/DiskCopyImage.java | 9 +- .../image/UniversalDiskImage.java | 9 +- .../org/applecommander/source/FileSource.java | 11 +- .../org/applecommander/source/Source.java | 109 +++++++++++++----- 6 files changed, 117 insertions(+), 38 deletions(-) diff --git a/lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/FileEntrySource.java b/lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/FileEntrySource.java index 303272dc..f37d322b 100644 --- a/lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/FileEntrySource.java +++ b/lib/ac-api/src/main/java/com/webcodepro/applecommander/storage/FileEntrySource.java @@ -26,6 +26,7 @@ import org.applecommander.util.Container; import org.applecommander.util.DataBuffer; import org.applecommander.util.InformationGroup; +import java.io.IOException; import java.util.List; import java.util.Optional; import java.util.Set; @@ -45,7 +46,9 @@ public class FileEntrySource implements Source { @Override public boolean can(Capability capability) { - // TODO? + if (fileEntry.getFormattedDisk().canWriteFileData()) { + return capability == Capability.SAVE_SOURCE; + } return false; } @@ -69,6 +72,16 @@ public class FileEntrySource implements Source { return fileEntry.getFilename(); } + @Override + public void save() throws IOException { + try { + fileEntry.setFileData(buffer.asBytes()); + changed = false; + } catch (DiskException ex) { + throw new IOException(ex); + } + } + @Override public DataBuffer readAllBytes() { return buffer; diff --git a/lib/ac-api/src/main/java/org/applecommander/archive/shrinkit/ShrinkitFileStore.java b/lib/ac-api/src/main/java/org/applecommander/archive/shrinkit/ShrinkitFileStore.java index 685d0f31..e9012ff9 100644 --- a/lib/ac-api/src/main/java/org/applecommander/archive/shrinkit/ShrinkitFileStore.java +++ b/lib/ac-api/src/main/java/org/applecommander/archive/shrinkit/ShrinkitFileStore.java @@ -37,6 +37,7 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.Optional; public class ShrinkitFileStore implements FileStore { @@ -45,6 +46,7 @@ public class ShrinkitFileStore implements FileStore { private final List files = new ArrayList<>(); public ShrinkitFileStore(Source source) { + Objects.requireNonNull(source); this.source = source; try { this.archive = new NuFileArchive(new ByteArrayInputStream(source.readAllBytes().asBytes())); diff --git a/lib/ac-api/src/main/java/org/applecommander/image/DiskCopyImage.java b/lib/ac-api/src/main/java/org/applecommander/image/DiskCopyImage.java index caf1764c..05c1b1e1 100644 --- a/lib/ac-api/src/main/java/org/applecommander/image/DiskCopyImage.java +++ b/lib/ac-api/src/main/java/org/applecommander/image/DiskCopyImage.java @@ -26,6 +26,7 @@ import org.applecommander.util.Container; import org.applecommander.util.DataBuffer; import org.applecommander.util.InformationGroup; +import java.io.IOException; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -82,8 +83,7 @@ public class DiskCopyImage implements Source { @Override public boolean can(Capability capability) { - // TODO - not definted yet - return false; + return source.can(capability); } @Override @@ -135,6 +135,11 @@ public class DiskCopyImage implements Source { source.clearChanges(); } + @Override + public void save() throws IOException { + source.save(); + } + @Override public List information() { BiFunction cksumFn = (a, e) -> { diff --git a/lib/ac-api/src/main/java/org/applecommander/image/UniversalDiskImage.java b/lib/ac-api/src/main/java/org/applecommander/image/UniversalDiskImage.java index 49f64c69..7d195bf4 100644 --- a/lib/ac-api/src/main/java/org/applecommander/image/UniversalDiskImage.java +++ b/lib/ac-api/src/main/java/org/applecommander/image/UniversalDiskImage.java @@ -26,6 +26,7 @@ import org.applecommander.util.Container; import org.applecommander.util.DataBuffer; import org.applecommander.util.InformationGroup; +import java.io.IOException; import java.util.List; import java.util.Optional; import java.util.Set; @@ -87,8 +88,7 @@ public class UniversalDiskImage implements Source { @Override public boolean can(Capability capability) { - // TODO we haven't defined any yet - return false; + return source.can(capability); } @Override @@ -136,6 +136,11 @@ public class UniversalDiskImage implements Source { source.clearChanges(); } + @Override + public void save() throws IOException { + source.save(); + } + @Override public List information() { Info info = getInfo(); diff --git a/lib/ac-api/src/main/java/org/applecommander/source/FileSource.java b/lib/ac-api/src/main/java/org/applecommander/source/FileSource.java index 31c1cf5e..b22c1311 100644 --- a/lib/ac-api/src/main/java/org/applecommander/source/FileSource.java +++ b/lib/ac-api/src/main/java/org/applecommander/source/FileSource.java @@ -28,6 +28,7 @@ import org.applecommander.util.InformationGroup; import java.io.*; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.util.List; import java.util.Optional; import java.util.zip.GZIPInputStream; @@ -67,7 +68,9 @@ public class FileSource implements Source { @Override public boolean can(Capability capability) { - // TODO - we haven't actually identified save yet! + if (path.toFile().canWrite()) { + return capability == Capability.SAVE_SOURCE; + } return false; } @@ -118,6 +121,12 @@ public class FileSource implements Source { changed = false; } + @Override + public void save() throws IOException { + Files.write(path, buffer.asBytes(), StandardOpenOption.TRUNCATE_EXISTING); + changed = false; + } + @Override public List information() { InformationGroup.Builder builder = InformationGroup.builder("File Source") diff --git a/lib/ac-api/src/main/java/org/applecommander/source/Source.java b/lib/ac-api/src/main/java/org/applecommander/source/Source.java index 5fd02f5c..26a9e285 100644 --- a/lib/ac-api/src/main/java/org/applecommander/source/Source.java +++ b/lib/ac-api/src/main/java/org/applecommander/source/Source.java @@ -25,53 +25,100 @@ import org.applecommander.util.Container; import org.applecommander.util.DataBuffer; import org.applecommander.util.InformationProvider; +import java.io.IOException; import java.util.Optional; -/** - * Source and Factory for an archive or disk in AppleCommander. - *

- * Typical usage: - * {@snippet lang = java: - * import com.webcodepro.applecommander.storage.Sources;Optional sourceOpt = Sources.create(Path.of(filename)); - * if (sourceOpt.isPresent()) { - * // do something with sourceOpt.get() - * System.out.println(sourceOpt.get().getSize()); - * } - * // Only happens if the initiating object is not understood - * throw new RuntimeException("Unable to create image source"); - *} - */ +/// Source and Factory for an archive or disk in AppleCommander. +/// +/// Typical usage: +/// +/// ```java +/// import com.webcodepro.applecommander.storage.Sources; +/// import java.util.Optional; +/// Optional sourceOpt = Sources.create(Path.of(filename)); +/// if (sourceOpt.isPresent()) { +/// // do something with the Source +/// Source source = sourceOpt.get(); +/// System.out.println(source.getSize()); +/// } +/// else { +/// // Only happens if the initiating object is not understood +/// throw new RuntimeException("Unable to create image source"); +/// } +/// ``` +/// +/// Optionally, you can use the Optional's more _fluent_ API and write +/// code like this: +/// +/// ```java +/// import com.webcodepro.applecommander.storage.Sources; +/// Sources.create(Path.of(filename)).ifPresent(source -> { +/// // do something with the Source +/// System.out.println(source.getSize()); +/// }); +/// ``` public interface Source extends CapabilityProvider, HintProvider, Container, InformationProvider { + /// Indicates the physical size of this image on disk. int getSize(); + + /// The name of the source. It likely will be used in user interfaces. It could represent a file + /// from the computer or even a file from a file store. String getName(); + + /// Read all bytes from this source. DataBuffer readAllBytes(); + + /// Read a range of bytes from this source. DataBuffer readBytes(int offset, int length); + + /// Write bytes to this source. Updates the changed status. void writeBytes(int offset, DataBuffer data); + + /// Indicates if this source has changed. boolean hasChanged(); + + /// Clears the changed indicator. void clearChanges(); - /** - * Indicates if the source image is approximately equal to this size - * (less any image over-read). - * Currently, hardcoded to allow up to 255 extra bytes at the end of a - * disk image. Must be at least the requested size! - */ + /// Save the image back to the origin. Note that this could conceivably save back to the + /// OS file system, or back to a file store. Use the `CapabilityProvider#can` to see if + /// a save is possible. The save operation should clear the change indicator. + /// + /// For example: + /// + /// ```java + /// if (source.can(Capability.SAVE_SOURCE)) { + /// source.save(); + /// } + /// ``` + /// + /// Note that the capability can be used to enable appropriate options. If source _cannot_ + /// be saved, a "Save As" type operation can be applied and the source switched for the + /// `FileStore` in question. + /// + /// @throws IOException if an error occurs while saving the source. + /// @throws UnsupportedOperationException if the source does not support the save operation. + default void save() throws IOException { + throw new UnsupportedOperationException("save operation not supported"); + } + + /// Indicates if the source image is approximately equal to this size + /// (less any image over-read). + /// Currently, hardcoded to allow up to 255 extra bytes at the end of a + /// disk image. Must be at least the requested size! default boolean isApproxEQ(int value) { return getSize() >= value && getSize() <= value + 255; } - /** - * Indicates if the source image is "approximately" between - * these two values (less any image over-read). - * Currently, hardcoded to allow up to 255 extra bytes at the end of a - * disk image. Must be at least the requested size! - */ + + /// Indicates if the source image is "approximately" between + /// these two values (less any image over-read). + /// Currently, hardcoded to allow up to 255 extra bytes at the end of a + /// disk image. Must be at least the requested size! default boolean isApproxBetween(int value1, int value2) { return getSize() >= value1 && getSize() <= value2 + 255; } - /** - * Test if this name has a given file extension. - */ + /// Test if this name has a given file extension. default boolean extensionLike(String extension) { String name = getName().toLowerCase(); String ext1 = String.format(".%s", extension.toLowerCase()); @@ -79,9 +126,7 @@ public interface Source extends CapabilityProvider, HintProvider, Container, Inf return name.endsWith(ext1) || name.endsWith(ext2); } - /** - * This is the Source creation factory interface. - */ + /// This is the Source creation factory interface. interface Factory { Optional fromObject(Object object); Optional fromSource(Source source);