diff --git a/api/README.md b/api/README.md index 5941e05..7971e3a 100644 --- a/api/README.md +++ b/api/README.md @@ -10,7 +10,7 @@ To include in a Maven project: net.sf.applecommander applesingle-api - 1.0.0 + 1.2.0 ``` @@ -19,7 +19,7 @@ To include in a Gradle project: ```groovy dependencies { // ... - compile "net.sf.applecommander:applesingle-api:1.0.0" + compile "net.sf.applecommander:applesingle-api:1.2.0" // ... } ``` @@ -58,3 +58,31 @@ as.save(file); ``` The `save(...)` method can save to a `File`, `Path`, or an `OutputStream`. + +## Entries + +If the higher-level API is insufficient, the lower-level API does allow either tracking of the processing +(see code for the `analyze` subcommand) or alternate processing of `Entry` objects (see the `filter` +subcommand). + +To tap into the `AppleSingleReader` events, add as many reporters as required. For example, the `analyze` +command uses these to display the details of the AppleSingle file as it is read: + +```java +AppleSingleReader reader = AppleSingleReader.builder(fileData) + .readAtReporter((start,chunk,desc) -> used.add(IntRange.of(start, start + chunk.length))) + .readAtReporter((start,chunk,desc) -> dumper.dump(start, chunk, desc)) + .versionReporter(this::reportVersion) + .numberOfEntriesReporter(this::reportNumberOfEntries) + .entryReporter(this::reportEntry) + .build(); +``` + +To work with the raw `Entry` objects, use the various `AppleSingle#asEntries` methods. For instance, the +`filter` subcommand bypasses the `AppleSingle` object altogether to implement the filter: + +```java +List entries = stdinFlag ? AppleSingle.asEntries(System.in) : AppleSingle.asEntries(inputFile); +// ... +AppleSingle.write(outputStream, newEntries); +``` 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 09628ac..bab2fbb 100644 --- a/api/src/main/java/io/github/applecommander/applesingle/AppleSingle.java +++ b/api/src/main/java/io/github/applecommander/applesingle/AppleSingle.java @@ -111,6 +111,10 @@ public class AppleSingle { } } + /** + * Common write capability for an AppleSingle. Also can be used by external entities to + * write a properly formatted AppleSingle file without the ProDOS assumptions of AppleSingle. + */ public static void write(OutputStream outputStream, List entries) throws IOException { final byte[] filler = new byte[16]; ByteBuffer buf = ByteBuffer.allocate(26).order(ByteOrder.BIG_ENDIAN); diff --git a/api/src/main/java/io/github/applecommander/applesingle/Entry.java b/api/src/main/java/io/github/applecommander/applesingle/Entry.java index dd9ccfe..0b7a4ff 100644 --- a/api/src/main/java/io/github/applecommander/applesingle/Entry.java +++ b/api/src/main/java/io/github/applecommander/applesingle/Entry.java @@ -6,6 +6,9 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Objects; +/** + * Represents an AppleSingle entry. + */ public class Entry { public static final int BYTES = 12; private int entryId; diff --git a/api/src/main/java/io/github/applecommander/applesingle/FileDatesInfo.java b/api/src/main/java/io/github/applecommander/applesingle/FileDatesInfo.java index 2f89473..b977556 100644 --- a/api/src/main/java/io/github/applecommander/applesingle/FileDatesInfo.java +++ b/api/src/main/java/io/github/applecommander/applesingle/FileDatesInfo.java @@ -6,7 +6,7 @@ import java.time.Instant; import java.util.function.IntSupplier; public class FileDatesInfo { - /** The number of seconds at the begining of the AppleSingle date epoch since the Unix epoch began. */ + /** The number of seconds at the beginning of the AppleSingle date epoch since the Unix epoch began. */ public static final Instant EPOCH_INSTANT = Instant.parse("2000-01-01T00:00:00.00Z"); /** Per the AppleSingle technical notes. */ public static final int UNKNOWN_DATE = 0x80000000; diff --git a/api/src/test/java/io/github/applecommander/applesingle/AppleSingleReaderTest.java b/api/src/test/java/io/github/applecommander/applesingle/AppleSingleReaderTest.java new file mode 100644 index 0000000..4efe52b --- /dev/null +++ b/api/src/test/java/io/github/applecommander/applesingle/AppleSingleReaderTest.java @@ -0,0 +1,72 @@ +package io.github.applecommander.applesingle; + +import org.junit.Test; +import static org.junit.Assert.*; + +import java.io.IOException; + +public class AppleSingleReaderTest { + @Test(expected = NullPointerException.class) + public void testDoesNotAcceptNull() { + AppleSingleReader.builder(null); + } + + @Test + public void testReporters() throws IOException { + Ticker versionCalled = new Ticker(); + Ticker numberOfEntriesCalled = new Ticker(); + Ticker entryReporterCalled = new Ticker(); + Ticker readAtCalled = new Ticker(); + // Intentionally calling ticker 2x to ensure events do get chained + AppleSingleReader r = AppleSingleReader.builder(SAMPLE_FILE) + .versionReporter(v -> versionCalled.tick()) + .versionReporter(v -> assertEquals(AppleSingle.VERSION_NUMBER2, v.intValue())) + .versionReporter(v -> versionCalled.tick()) + .numberOfEntriesReporter(n -> numberOfEntriesCalled.tick()) + .numberOfEntriesReporter(n -> assertEquals(1, n.intValue())) + .numberOfEntriesReporter(n -> numberOfEntriesCalled.tick()) + .readAtReporter((o,b,d) -> readAtCalled.tick()) + .readAtReporter((o,b,d) -> readAtCalled.tick()) + .entryReporter(e -> entryReporterCalled.tick()) + .entryReporter(e -> assertEquals("Hello, World!\n", new String(e.getData()))) + .entryReporter(e -> assertEquals(e.getEntryId(), EntryType.DATA_FORK.entryId)) + .entryReporter(e -> entryReporterCalled.tick()) + .build(); + // Executes on the reader + AppleSingle.asEntries(r); + // Validate + assertEquals(2, versionCalled.count()); + assertEquals(2, numberOfEntriesCalled.count()); + assertEquals(2, entryReporterCalled.count()); + assertTrue(readAtCalled.count() >= 2); + } + + /** + * AppleSingle file with a simple Data Fork and nothing else. + *
+ * + * $ echo "Hello, World!" | asu create --stdin-fork=data --filetype=txt --stdout | asu filter --include=1 --stdin --stdout | hexdump -C + * 00000000 00 05 16 00 00 02 00 00 00 00 00 00 00 00 00 00 |................| + * 00000010 00 00 00 00 00 00 00 00 00 01 00 00 00 01 00 00 |................| + * 00000020 00 26 00 00 00 0e 48 65 6c 6c 6f 2c 20 57 6f 72 |.&....Hello, Wor| + * 00000030 6c 64 21 0a |ld!.| + * + */ + public static final byte[] SAMPLE_FILE = { + 0x00, 0x05, 0x16, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x26, 0x00, 0x00, 0x00, 0x0e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x57, 0x6f, 0x72, + 0x6c, 0x64, 0x21, 0x0a + }; + + public static class Ticker { + private int count; + + public void tick() { + this.count++; + } + public int count() { + return this.count; + } + } +} diff --git a/tools/asu/README.md b/tools/asu/README.md index 241d8c9..8069b84 100644 --- a/tools/asu/README.md +++ b/tools/asu/README.md @@ -4,13 +4,13 @@ For the included command-line utility, we are using `asu` for the name. `as` is the GNU Assembler while `applesingle` is already on Macintoshes. Hopefully that will prevent some confusion! -Note that all runs are with the `asu` alias defined as `alias asu='java -jar build/libs/applesingle-1.0.0.jar'` +Note that all runs are with the `asu` alias defined as `alias asu='java -jar build/libs/applesingle-1.2.0.jar'` (adjust as necessary). ## Basic usage ```shell -$ asu +$ asu --help Usage: asu [-hV] [--debug] [COMMAND] AppleSingle utility @@ -21,10 +21,12 @@ Options: -V, --version Print version information and exit. Commands: - help Displays help information about the specified command - info Display information about an AppleSingle file + analyze Perform an analysis on an AppleSingle file create Create an AppleSingle file extract Extract contents of an AppleSingle file + filter Filter an AppleSingle file + help Displays help information about the specified command + info Display information about an AppleSingle file ``` ## Subcommand help diff --git a/tools/asu/src/main/java/io/github/applecommander/applesingle/tools/asu/AnalyzeCommand.java b/tools/asu/src/main/java/io/github/applecommander/applesingle/tools/asu/AnalyzeCommand.java index 5e6bebc..3770344 100644 --- a/tools/asu/src/main/java/io/github/applecommander/applesingle/tools/asu/AnalyzeCommand.java +++ b/tools/asu/src/main/java/io/github/applecommander/applesingle/tools/asu/AnalyzeCommand.java @@ -54,7 +54,7 @@ public class AnalyzeCommand implements Callable { List used = new ArrayList<>(); HexDumper dumper = HexDumper.standard(); AppleSingleReader reader = AppleSingleReader.builder(fileData) - .readAtReporter((start,b,d) -> used.add(IntRange.of(start, start + b.length))) + .readAtReporter((start,chunk,desc) -> used.add(IntRange.of(start, start + chunk.length))) .readAtReporter((start,chunk,desc) -> dumper.dump(start, chunk, desc)) .versionReporter(this::reportVersion) .numberOfEntriesReporter(this::reportNumberOfEntries)