From cebb3727b0125c9bf482b454daa03649ac0fb59f Mon Sep 17 00:00:00 2001 From: Rob Greene Date: Wed, 7 Feb 2024 18:54:43 -0600 Subject: [PATCH] Adding an AppleSingle edit command. --- .../applesingle/AppleSingle.java | 11 +- gradle.properties | 2 +- .../applesingle/tools/asu/EditCommand.java | 167 ++++++++++++++++++ .../applesingle/tools/asu/Main.java | 3 +- 4 files changed, 180 insertions(+), 3 deletions(-) create mode 100644 tools/asu/src/main/java/io/github/applecommander/applesingle/tools/asu/EditCommand.java diff --git a/api/src/main/java/io/github/applecommander/applesingle/AppleSingle.java b/api/src/main/java/io/github/applecommander/applesingle/AppleSingle.java index f5b68d2..643f333 100644 --- a/api/src/main/java/io/github/applecommander/applesingle/AppleSingle.java +++ b/api/src/main/java/io/github/applecommander/applesingle/AppleSingle.java @@ -243,8 +243,17 @@ public class AppleSingle { public static Builder builder() { return new Builder(); } + public static Builder builder(AppleSingle original) { + return new Builder(original); + } public static class Builder { - private AppleSingle as = new AppleSingle(); + final private AppleSingle as; + private Builder() { + this.as = new AppleSingle(); + } + private Builder(AppleSingle original) { + this.as = original; + } public Builder realName(String realName) { if (!Character.isAlphabetic(realName.charAt(0))) { throw new IllegalArgumentException("ProDOS file names must begin with a letter"); diff --git a/gradle.properties b/gradle.properties index 1eb7ba6..12e134f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ # Universal applesingle version number. Used for: # - Naming JAR file. # - The build will insert this into a file that is read at run time as well. -version=1.2.2 +version=1.3.0 # Maven Central Repository G and A of GAV coordinate. :-) group=net.sf.applecommander diff --git a/tools/asu/src/main/java/io/github/applecommander/applesingle/tools/asu/EditCommand.java b/tools/asu/src/main/java/io/github/applecommander/applesingle/tools/asu/EditCommand.java new file mode 100644 index 0000000..172a177 --- /dev/null +++ b/tools/asu/src/main/java/io/github/applecommander/applesingle/tools/asu/EditCommand.java @@ -0,0 +1,167 @@ +package io.github.applecommander.applesingle.tools.asu; + +import io.github.applecommander.applesingle.AppleSingle; +import io.github.applecommander.applesingle.Utilities; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; +import picocli.CommandLine.Parameters; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.time.Instant; +import java.util.Optional; +import java.util.concurrent.Callable; + +/** + * Supports editing of AppleSingle archives. + */ +@Command(name = "edit", description = { "Edit an AppleSingle file" }, + parameterListHeading = "%nParameters:%n", + descriptionHeading = "%n", + footerHeading = "%nNotes:%n", + footer = { "* Dates should be supplied like '2007-12-03T10:15:30.00Z'.", + "* 'Known' ProDOS file types: TXT, BIN, INT, BAS, REL, SYS.", + "* Include the output file or specify stdout" }, + optionListHeading = "%nOptions:%n") +public class EditCommand implements Callable { + @Option(names = { "-h", "--help" }, description = "Show help for subcommand", usageHelp = true) + private boolean helpFlag; + + @Option(names = "--stdin", description = "Read AppleSingle file from stdin") + private boolean stdinFlag; + @Option(names = "--stdout", description = "Write AppleSingle file to stdout") + private boolean stdoutFlag; + + @Option(names = "--stdin-fork", description = "Read fork from stdin (specify data or resource)") + private ForkType stdinForkType; + + @Option(names = "--fix-text", description = "Set the high bit and fix line endings") + private boolean fixTextFlag; + + @Option(names = "--data-fork", description = "Read data fork from file") + private Path dataForkFile; + + @Option(names = "--resource-fork", description = "Read resource fork from file") + private Path resourceForkFile; + + @Option(names = "--name", description = "Set the filename (defaults to name of data fork, if supplied)") + private String realName; + + @Option(names = "--access", description = "Set the ProDOS access flags", converter = IntegerTypeConverter.class) + private Integer access; + + @Option(names = "--filetype", description = "Set the ProDOS file type", converter = ProdosFileTypeConverter.class) + private Integer filetype; + + @Option(names = "--auxtype", description = "Set the ProDOS auxtype", converter = IntegerTypeConverter.class) + private Integer auxtype; + + @Option(names = "--creation-date", description = "Set the file creation date") + private Instant creationDate; + @Option(names = "--modification-date", description = "Set the file modification date") + private Instant modificationDate; + @Option(names = "--backup-date", description = "Set the file backup date") + private Instant backupDate; + @Option(names = "--access-date", description = "Set the file access date") + private Instant accessDate; + + @Parameters(arity = "0..1", description = "AppleSingle file to modify") + private Path file; + + @Override + public Void call() throws IOException { + validateArguments(); + + AppleSingle original = stdinFlag ? AppleSingle.read(System.in) : AppleSingle.read(file); + + byte[] dataFork = prepDataFork(); + byte[] resourceFork = prepResourceFork(); + + AppleSingle applesingle = buildAppleSingle(original, dataFork, resourceFork); + writeAppleSingle(applesingle); + + return null; + } + + public void validateArguments() throws IOException { + if ((stdinFlag && file != null) || (!stdinFlag && file == null)) { + throw new IOException("Please choose one of stdin or input file for original"); + } + if ((dataForkFile != null && stdinForkType == ForkType.data) + || (resourceForkFile != null && stdinForkType == ForkType.resource)) { + throw new IOException("Stdin only supports one type of fork for input"); + } + if (stdinForkType == ForkType.both) { + throw new IOException("Unable to read two forks from stdin"); + } + } + + public byte[] prepDataFork() throws IOException { + byte[] dataFork = null; + if (stdinForkType == ForkType.data) { + dataFork = Utilities.toByteArray(System.in); + } else if (dataForkFile != null) { + dataFork = Files.readAllBytes(dataForkFile); + } + + if (fixTextFlag && dataFork != null) { + for (int i=0; i