Adding an AppleSingle edit command.

This commit is contained in:
Rob Greene 2024-02-07 18:54:43 -06:00
parent 9d676a5f44
commit cebb3727b0
4 changed files with 180 additions and 3 deletions

View File

@ -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");

View File

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

View File

@ -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<Void> {
@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<dataFork.length; i++) {
if (dataFork[i] == '\n') dataFork[i] = 0x0d;
dataFork[i] = (byte)(dataFork[i] | 0x80);
}
}
return dataFork;
}
public byte[] prepResourceFork() throws IOException {
byte[] resourceFork = null;
if (stdinForkType == ForkType.resource) {
resourceFork = Utilities.toByteArray(System.in);
} else if (resourceForkFile != null) {
resourceFork = Files.readAllBytes(resourceForkFile);
}
return resourceFork;
}
public AppleSingle buildAppleSingle(AppleSingle original,
byte[] dataFork,
byte[] resourceFork) throws IOException {
AppleSingle.Builder builder = AppleSingle.builder(original);
if (realName != null) {
builder.realName(realName);
} else if (dataForkFile != null) {
String name = dataForkFile.getFileName().toString();
builder.realName(name);
}
if (access != null) builder.access(access.intValue());
if (filetype != null) builder.fileType(filetype.intValue());
if (auxtype != null) builder.auxType(auxtype.intValue());
if (dataFork != null) builder.dataFork(dataFork);
if (resourceFork != null) builder.resourceFork(resourceFork);
if (dataForkFile != null || resourceForkFile != null) {
Path path = Optional.ofNullable(dataForkFile).orElse(resourceForkFile);
BasicFileAttributes attribs = Files.readAttributes(path, BasicFileAttributes.class);
builder.creationDate(attribs.creationTime().toInstant());
builder.modificationDate(attribs.lastModifiedTime().toInstant());
builder.accessDate(attribs.lastAccessTime().toInstant());
}
if (creationDate != null) builder.creationDate(creationDate);
if (modificationDate != null) builder.modificationDate(modificationDate);
if (backupDate != null) builder.backupDate(backupDate);
if (accessDate != null) builder.accessDate(accessDate);
return builder.build();
}
public void writeAppleSingle(AppleSingle applesingle) throws IOException {
if (stdoutFlag) {
applesingle.save(System.out);
} else {
applesingle.save(file);
System.out.printf("Saved to '%s'.\n", file);
}
}
}

View File

@ -17,7 +17,8 @@ import picocli.CommandLine.Option;
description = "AppleSingle utility",
subcommands = {
AnalyzeCommand.class,
CreateCommand.class,
CreateCommand.class,
EditCommand.class,
ExtractCommand.class,
FilterCommand.class,
HelpCommand.class,