Adding Filter and altering AppleSingle write mechanism to be more
reusable.
This commit is contained in:
parent
4f34a8a289
commit
4627e08f9f
|
@ -27,6 +27,7 @@ import java.util.function.Consumer;
|
|||
* 1. Data Fork<br/>
|
||||
* 2. Resource Fork<br/>
|
||||
* 3. Real Name<br/>
|
||||
* 8. File Dates Info<br/>
|
||||
* 11. ProDOS File Info<br/>
|
||||
*
|
||||
* @see <a href="https://github.com/AppleCommander/AppleCommander/issues/20">AppleCommander issue #20</a>
|
||||
|
@ -80,28 +81,24 @@ public class AppleSingle {
|
|||
}
|
||||
|
||||
public void save(OutputStream outputStream) throws IOException {
|
||||
final boolean hasResourceFork = Objects.nonNull(resourceFork);
|
||||
final boolean hasRealName = Objects.nonNull(realName);
|
||||
final int entries = 3 + (hasRealName ? 1 : 0) + (hasResourceFork ? 1 : 0);
|
||||
|
||||
int realNameOffset = 26 + (12 * entries);
|
||||
int prodosFileInfoOffset = realNameOffset + (hasRealName ? realName.length() : 0);
|
||||
int fileDatesInfoOffset = prodosFileInfoOffset + 8;
|
||||
int resourceForkOffset = fileDatesInfoOffset + 16;
|
||||
int dataForkOffset = resourceForkOffset + (hasResourceFork ? resourceFork.length : 0);
|
||||
|
||||
writeFileHeader(outputStream, entries);
|
||||
if (hasRealName) writeHeader(outputStream, 3, realNameOffset, realName.length());
|
||||
writeHeader(outputStream, 11, prodosFileInfoOffset, 8);
|
||||
writeHeader(outputStream, 8, fileDatesInfoOffset, 16);
|
||||
if (hasResourceFork) writeHeader(outputStream, 2, resourceForkOffset, resourceFork.length);
|
||||
writeHeader(outputStream, 1, dataForkOffset, dataFork.length);
|
||||
|
||||
if (hasRealName) writeRealName(outputStream);
|
||||
writeProdosFileInfo(outputStream);
|
||||
writeFileDatesInfo(outputStream);
|
||||
if (hasResourceFork) writeResourceFork(outputStream);
|
||||
writeDataFork(outputStream);
|
||||
List<Entry> entries = new ArrayList<>();
|
||||
Optional.ofNullable(this.realName)
|
||||
.map(String::getBytes)
|
||||
.map(b -> Entry.create(EntryType.REAL_NAME, b))
|
||||
.ifPresent(entries::add);
|
||||
Optional.ofNullable(this.prodosFileInfo)
|
||||
.map(ProdosFileInfo::toEntry)
|
||||
.ifPresent(entries::add);
|
||||
Optional.ofNullable(this.fileDatesInfo)
|
||||
.map(FileDatesInfo::toEntry)
|
||||
.ifPresent(entries::add);
|
||||
Optional.ofNullable(this.resourceFork)
|
||||
.map(b -> Entry.create(EntryType.RESOURCE_FORK, b))
|
||||
.ifPresent(entries::add);
|
||||
Optional.ofNullable(this.dataFork)
|
||||
.map(b -> Entry.create(EntryType.DATA_FORK, b))
|
||||
.ifPresent(entries::add);
|
||||
write(outputStream, entries);
|
||||
}
|
||||
public void save(File file) throws IOException {
|
||||
try (FileOutputStream outputStream = new FileOutputStream(file)) {
|
||||
|
@ -114,46 +111,24 @@ public class AppleSingle {
|
|||
}
|
||||
}
|
||||
|
||||
private void writeFileHeader(OutputStream outputStream, int numberOfEntries) throws IOException {
|
||||
public static void write(OutputStream outputStream, List<Entry> entries) throws IOException {
|
||||
final byte[] filler = new byte[16];
|
||||
ByteBuffer buf = ByteBuffer.allocate(26).order(ByteOrder.BIG_ENDIAN);
|
||||
buf.putInt(MAGIC_NUMBER);
|
||||
buf.putInt(VERSION_NUMBER2);
|
||||
buf.put(filler);
|
||||
buf.putShort((short)numberOfEntries);
|
||||
buf.putShort((short)entries.size());
|
||||
outputStream.write(buf.array());
|
||||
}
|
||||
private void writeHeader(OutputStream outputStream, int entryId, int offset, int length) throws IOException {
|
||||
ByteBuffer buf = ByteBuffer.allocate(12).order(ByteOrder.BIG_ENDIAN);
|
||||
buf.putInt(entryId);
|
||||
buf.putInt(offset);
|
||||
buf.putInt(length);
|
||||
outputStream.write(buf.array());
|
||||
}
|
||||
private void writeRealName(OutputStream outputStream) throws IOException {
|
||||
outputStream.write(realName.getBytes());
|
||||
}
|
||||
private void writeProdosFileInfo(OutputStream outputStream) throws IOException {
|
||||
ByteBuffer buf = ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN);
|
||||
buf.putShort((short)prodosFileInfo.access);
|
||||
buf.putShort((short)prodosFileInfo.fileType);
|
||||
buf.putInt(prodosFileInfo.auxType);
|
||||
outputStream.write(buf.array());
|
||||
}
|
||||
private void writeFileDatesInfo(OutputStream outputStream) throws IOException {
|
||||
ByteBuffer buf = ByteBuffer.allocate(16).order(ByteOrder.BIG_ENDIAN);
|
||||
buf.putInt(fileDatesInfo.getCreation());
|
||||
buf.putInt(fileDatesInfo.getModification());
|
||||
buf.putInt(fileDatesInfo.getBackup());
|
||||
buf.putInt(fileDatesInfo.getAccess());
|
||||
outputStream.write(buf.array());
|
||||
}
|
||||
private void writeResourceFork(OutputStream outputStream) throws IOException {
|
||||
outputStream.write(resourceFork);
|
||||
}
|
||||
private void writeDataFork(OutputStream outputStream) throws IOException {
|
||||
outputStream.write(dataFork);
|
||||
}
|
||||
|
||||
int offset = 26 + (Entry.BYTES * entries.size());
|
||||
for (Entry entry : entries) {
|
||||
entry.writeHeader(outputStream, offset);
|
||||
offset += entry.getLength();
|
||||
}
|
||||
for (Entry entry : entries) {
|
||||
entry.writeData(outputStream);
|
||||
}
|
||||
}
|
||||
|
||||
public static AppleSingle read(InputStream inputStream) throws IOException {
|
||||
Objects.requireNonNull(inputStream, "Please supply an input stream");
|
||||
|
@ -186,7 +161,7 @@ public class AppleSingle {
|
|||
}
|
||||
public static List<Entry> asEntries(byte[] data) throws IOException {
|
||||
Objects.requireNonNull(data);
|
||||
return asEntries(AppleSingleReader.builder().data(data).build());
|
||||
return asEntries(AppleSingleReader.builder(data).build());
|
||||
}
|
||||
public static List<Entry> asEntries(AppleSingleReader reader) throws IOException {
|
||||
Objects.requireNonNull(reader);
|
||||
|
|
|
@ -5,13 +5,20 @@ import java.nio.ByteOrder;
|
|||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* The AppleSingleReader is a component that allows tools to react to processing that
|
||||
* goes on when an AppleSingle file is being read. The {@code Builder} allows multiple
|
||||
* {@code Consumer}'s and {@code ReadAtReporter}'s to be defined.
|
||||
*/
|
||||
public final class AppleSingleReader {
|
||||
private AppleSingleReader() { /* Prevent construction */ }
|
||||
|
||||
private byte[] data;
|
||||
private int pos = 0;
|
||||
private Consumer<Integer> versionReporter = v -> {};
|
||||
private Consumer<Integer> numberOfEntriesReporter = n -> {};
|
||||
private Consumer<Entry> entryReporter = e -> {};
|
||||
private ReadAtReporter readAtReporter = (s,l,b,d) -> {};
|
||||
private ReadAtReporter readAtReporter = (s,b,d) -> {};
|
||||
|
||||
public ByteBuffer read(int len, String description) {
|
||||
try {
|
||||
|
@ -23,7 +30,7 @@ public final class AppleSingleReader {
|
|||
public ByteBuffer readAt(int start, int len, String description) {
|
||||
byte[] chunk = new byte[len];
|
||||
System.arraycopy(data, start, chunk, 0, len);
|
||||
readAtReporter.accept(start, len, chunk, description);
|
||||
readAtReporter.accept(start, chunk, description);
|
||||
ByteBuffer buffer = ByteBuffer.wrap(chunk)
|
||||
.order(ByteOrder.BIG_ENDIAN);
|
||||
return buffer;
|
||||
|
@ -38,41 +45,41 @@ public final class AppleSingleReader {
|
|||
entryReporter.accept(entry);
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
/** Create a {@code Builder} for an {@code AppleSingleReader}. */
|
||||
public static Builder builder(byte[] data) {
|
||||
return new Builder(data);
|
||||
}
|
||||
public static class Builder {
|
||||
private AppleSingleReader reader = new AppleSingleReader();
|
||||
private Builder() {
|
||||
// Prevent construction
|
||||
}
|
||||
public Builder data(byte[] data) {
|
||||
Objects.requireNonNull(data);
|
||||
private Builder(byte[] data) {
|
||||
Objects.requireNonNull(data, "You must supply a byte[] of data");
|
||||
reader.data = data;
|
||||
return this;
|
||||
}
|
||||
/** Add a version reporter. Note that multiple can be added. */
|
||||
public Builder versionReporter(Consumer<Integer> consumer) {
|
||||
Objects.requireNonNull(consumer);
|
||||
reader.versionReporter = reader.versionReporter.andThen(consumer);
|
||||
return this;
|
||||
}
|
||||
/** Add a number of entries reporter. Note that multiple can be added. */
|
||||
public Builder numberOfEntriesReporter(Consumer<Integer> consumer) {
|
||||
Objects.requireNonNull(consumer);
|
||||
reader.numberOfEntriesReporter = reader.numberOfEntriesReporter.andThen(consumer);
|
||||
return this;
|
||||
}
|
||||
/** Add an entry reporter. Note that multiple can be added. */
|
||||
public Builder entryReporter(Consumer<Entry> consumer) {
|
||||
Objects.requireNonNull(consumer);
|
||||
reader.entryReporter = reader.entryReporter.andThen(consumer);
|
||||
return this;
|
||||
}
|
||||
/** Add a read at reporter. Note that multiple can be added. */
|
||||
public Builder readAtReporter(ReadAtReporter consumer) {
|
||||
Objects.requireNonNull(consumer);
|
||||
reader.readAtReporter = reader.readAtReporter.andThen(consumer);
|
||||
return this;
|
||||
}
|
||||
public AppleSingleReader build() {
|
||||
Objects.requireNonNull(reader.data, "You must supply a byte[] of data");
|
||||
return reader;
|
||||
}
|
||||
}
|
||||
|
@ -82,10 +89,30 @@ public final class AppleSingleReader {
|
|||
* heaviliy modeled on the {@code Consumer} interface.
|
||||
*/
|
||||
public interface ReadAtReporter {
|
||||
public void accept(int start, int len, byte[] data, String description);
|
||||
default ReadAtReporter andThen(ReadAtReporter after) {
|
||||
/**
|
||||
* Performs this operation on the given arguments.
|
||||
*
|
||||
* @param start the offset into the file
|
||||
* @param data the specific data being processed
|
||||
* @param description descriptive text regarding the data
|
||||
*/
|
||||
public void accept(int start, byte[] data, String description);
|
||||
|
||||
/**
|
||||
* Returns a composed {@code ReadAtReporter} that performs, in sequence, this
|
||||
* operation followed by the {@code after} operation. If performing either
|
||||
* operation throws an exception, it is relayed to the caller of the
|
||||
* composed operation. If performing this operation throws an exception,
|
||||
* the {@code after} operation will not be performed.
|
||||
*
|
||||
* @param after the operation to perform after this operation
|
||||
* @return a composed {@code ReadAtReporter} that performs in sequence this
|
||||
* operation followed by the {@code after} operation
|
||||
* @throws NullPointerException if {@code after} is null
|
||||
*/
|
||||
public default ReadAtReporter andThen(ReadAtReporter after) {
|
||||
Objects.requireNonNull(after);
|
||||
return (s,l,b,d) -> { accept(s,l,b,d); after.accept(s,l,b,d); };
|
||||
return (s,b,d) -> { accept(s,b,d); after.accept(s,b,d); };
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +1,13 @@
|
|||
package io.github.applecommander.applesingle;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Objects;
|
||||
|
||||
public class Entry {
|
||||
public static final int LENGTH = 12;
|
||||
public static final int BYTES = 12;
|
||||
private int entryId;
|
||||
private int offset;
|
||||
private int length;
|
||||
|
@ -14,7 +16,7 @@ public class Entry {
|
|||
public static Entry create(AppleSingleReader reader) {
|
||||
Objects.requireNonNull(reader);
|
||||
|
||||
ByteBuffer buffer = reader.read(LENGTH, "Entry header");
|
||||
ByteBuffer buffer = reader.read(BYTES, "Entry header");
|
||||
Entry entry = new Entry();
|
||||
entry.entryId = buffer.getInt();
|
||||
entry.offset = buffer.getInt();
|
||||
|
@ -49,4 +51,16 @@ public class Entry {
|
|||
public ByteBuffer getBuffer() {
|
||||
return ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN).asReadOnlyBuffer();
|
||||
}
|
||||
|
||||
public void writeHeader(OutputStream outputStream, int offset) throws IOException {
|
||||
this.offset = offset;
|
||||
ByteBuffer buf = ByteBuffer.allocate(BYTES).order(ByteOrder.BIG_ENDIAN);
|
||||
buf.putInt(this.entryId);
|
||||
buf.putInt(this.offset);
|
||||
buf.putInt(this.length);
|
||||
outputStream.write(buf.array());
|
||||
}
|
||||
public void writeData(OutputStream outputStream) throws IOException {
|
||||
outputStream.write(data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
package io.github.applecommander.applesingle;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
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. */
|
||||
//public static final int EPOCH_2000 = 946684800;
|
||||
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;
|
||||
/** Number of bytes a File Dates Info takes per AppleSingle spec. */
|
||||
public static final int BYTES = 16;
|
||||
|
||||
// Package scoped so AppleSingle Builder is able to set
|
||||
int creation;
|
||||
int modification;
|
||||
int backup;
|
||||
|
@ -42,6 +45,15 @@ public class FileDatesInfo {
|
|||
this.access = access;
|
||||
}
|
||||
|
||||
public Entry toEntry() {
|
||||
ByteBuffer buf = ByteBuffer.allocate(BYTES).order(ByteOrder.BIG_ENDIAN);
|
||||
buf.putInt(creation);
|
||||
buf.putInt(modification);
|
||||
buf.putInt(backup);
|
||||
buf.putInt(access);
|
||||
return Entry.create(EntryType.FILE_DATES_INFO, buf.array());
|
||||
}
|
||||
|
||||
public Instant getCreationInstant() {
|
||||
return toInstant(this::getCreation);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package io.github.applecommander.applesingle;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
import io.github.applecommander.applesingle.AppleSingle.Builder;
|
||||
|
||||
|
@ -11,6 +12,10 @@ import io.github.applecommander.applesingle.AppleSingle.Builder;
|
|||
* Note 2: Fields are package-private to allow {@link Builder} to have direct access.<br/>
|
||||
*/
|
||||
public class ProdosFileInfo {
|
||||
/** Number of bytes a File Dates Info takes per AppleSingle spec. */
|
||||
public static final int BYTES = 8;
|
||||
|
||||
// Package scoped so AppleSingle Builder is able to set
|
||||
int access;
|
||||
int fileType;
|
||||
int auxType;
|
||||
|
@ -31,7 +36,15 @@ public class ProdosFileInfo {
|
|||
this.fileType = fileType;
|
||||
this.auxType = auxType;
|
||||
}
|
||||
|
||||
|
||||
public Entry toEntry() {
|
||||
ByteBuffer buf = ByteBuffer.allocate(BYTES).order(ByteOrder.BIG_ENDIAN);
|
||||
buf.putShort((short)access);
|
||||
buf.putShort((short)fileType);
|
||||
buf.putInt(auxType);
|
||||
return Entry.create(EntryType.PRODOS_FILE_INFO, buf.array());
|
||||
}
|
||||
|
||||
public int getAccess() {
|
||||
return access;
|
||||
}
|
||||
|
|
|
@ -53,10 +53,9 @@ public class AnalyzeCommand implements Callable<Void> {
|
|||
|
||||
List<IntRange> used = new ArrayList<>();
|
||||
HexDumper dumper = HexDumper.standard();
|
||||
AppleSingleReader reader = AppleSingleReader.builder()
|
||||
.data(fileData)
|
||||
.readAtReporter((start,len,b,d) -> used.add(IntRange.of(start, start+len)))
|
||||
.readAtReporter((start,len,chunk,desc) -> dumper.dump(start, chunk, desc))
|
||||
AppleSingleReader reader = AppleSingleReader.builder(fileData)
|
||||
.readAtReporter((start,b,d) -> used.add(IntRange.of(start, start + b.length)))
|
||||
.readAtReporter((start,chunk,desc) -> dumper.dump(start, chunk, desc))
|
||||
.versionReporter(this::reportVersion)
|
||||
.numberOfEntriesReporter(this::reportNumberOfEntries)
|
||||
.entryReporter(this::reportEntry)
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
package io.github.applecommander.applesingle.tools.asu;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import io.github.applecommander.applesingle.AppleSingle;
|
||||
import io.github.applecommander.applesingle.Entry;
|
||||
import io.github.applecommander.applesingle.EntryType;
|
||||
import picocli.CommandLine.Command;
|
||||
import picocli.CommandLine.Option;
|
||||
import picocli.CommandLine.Parameters;
|
||||
|
||||
/**
|
||||
* Allow filtering of an an AppleSingle archive.
|
||||
* Both source and target can be a file or stream.
|
||||
*/
|
||||
@Command(name = "filter", description = { "Filter an AppleSingle file",
|
||||
"Please include a file name or indicate stdin should be read, but not both." },
|
||||
parameterListHeading = "%nParameters:%n",
|
||||
descriptionHeading = "%n",
|
||||
optionListHeading = "%nOptions:%n")
|
||||
public class FilterCommand implements Callable<Void> {
|
||||
@Option(names = { "-h", "--help" }, description = "Show help for subcommand", usageHelp = true)
|
||||
private boolean helpFlag;
|
||||
|
||||
@Option(names = "--stdin", description = "Read AppleSingle from stdin.")
|
||||
private boolean stdinFlag;
|
||||
|
||||
@Option(names = "--stdout", description = "Write AppleSingle to stdout.")
|
||||
private boolean stdoutFlag;
|
||||
|
||||
@Option(names = { "-o", "--output" }, description = "Write AppleSingle to file.")
|
||||
private Path outputFile;
|
||||
|
||||
@Parameters(arity = "0..1", description = "File to process")
|
||||
private Path inputFile;
|
||||
|
||||
@Option(names = "--prodos", description = "Apply ProDOS specific filter")
|
||||
private boolean prodosFlag;
|
||||
@Option(names = { "--mac", "--macintosh" }, description = "Apply Macintosh specific filter")
|
||||
private boolean macintoshFlag;
|
||||
@Option(names = "--msdos", description = "Apply MS-DOS specific filter")
|
||||
private boolean msdosFlag;
|
||||
@Option(names = "--afp", description = "Apply AFP specific filter")
|
||||
private boolean afpFlag;
|
||||
|
||||
@Option(names = "--include", description = "Filter by including specific entryIds", split = ",")
|
||||
private Integer[] includeEntryIds;
|
||||
|
||||
@Option(names = "--exclude", description = "Filter by excluding specific entryIds", split = ",")
|
||||
private Integer[] excludeEntryIds;
|
||||
|
||||
@Override
|
||||
public Void call() throws IOException {
|
||||
try (PrintStream ps = this.stdoutFlag ? new PrintStream(NullOutputStream.INSTANCE) : System.out) {
|
||||
OSFilter osFilter = validate();
|
||||
|
||||
SortedSet<Integer> included = toSet(includeEntryIds, osFilter);
|
||||
SortedSet<Integer> excluded = toSet(excludeEntryIds, null);
|
||||
List<Entry> entries = stdinFlag ? AppleSingle.asEntries(System.in) : AppleSingle.asEntries(inputFile);
|
||||
List<Entry> newEntries = entries.stream()
|
||||
.filter(e -> included.isEmpty() || included.contains(e.getEntryId()))
|
||||
.filter(e -> excluded.isEmpty() || !excluded.contains(e.getEntryId()))
|
||||
.collect(Collectors.toList());
|
||||
// Check if we ended up with different things
|
||||
SortedSet<EntryType> before = toEntryType(entries);
|
||||
SortedSet<EntryType> after = toEntryType(newEntries);
|
||||
before.removeAll(after); // Note: modifies before
|
||||
if (!before.isEmpty()) {
|
||||
ps.printf("Removed the following entries:\n");
|
||||
before.forEach(e -> ps.printf("- %s\n", e.name));
|
||||
} else {
|
||||
ps.printf("No entries removed.\n");
|
||||
}
|
||||
|
||||
OutputStream outputStream = stdoutFlag ? System.out : Files.newOutputStream(outputFile);
|
||||
AppleSingle.write(outputStream, newEntries);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
private OSFilter validate() throws IOException {
|
||||
long count = Stream.of(prodosFlag, macintoshFlag, msdosFlag, afpFlag).filter(flag -> flag).count();
|
||||
// Expected boundaries
|
||||
if (count == 0) return null;
|
||||
if (count > 1) throw new IOException("Please choose only one operating system flag!");
|
||||
// Set the correct OS Flag
|
||||
if (prodosFlag) return OSFilter.PRODOS;
|
||||
if (macintoshFlag) return OSFilter.MACINTOSH;
|
||||
if (msdosFlag) return OSFilter.MS_DOS;
|
||||
if (afpFlag) return OSFilter.AFP;
|
||||
// Not a clue how you can get here...
|
||||
throw new IOException("Bug! Please put in a ticket or a pull request. Thanks! :-)");
|
||||
}
|
||||
private SortedSet<Integer> toSet(Integer[] entryIds, OSFilter filter) {
|
||||
SortedSet<Integer> set = new TreeSet<>();
|
||||
Optional.ofNullable(entryIds)
|
||||
.map(a -> Arrays.asList(a))
|
||||
.ifPresent(set::addAll);
|
||||
Optional.ofNullable(filter)
|
||||
.map(f -> f.types)
|
||||
.ifPresent(t -> Stream.of(t)
|
||||
.map(e -> e.entryId)
|
||||
.collect(Collectors.toCollection(() -> set)));
|
||||
return set;
|
||||
}
|
||||
private SortedSet<EntryType> toEntryType(Collection<Entry> entries) {
|
||||
return entries.stream()
|
||||
.map(e -> e.getEntryId())
|
||||
.map(EntryType::find)
|
||||
.collect(Collectors.toCollection(() -> new TreeSet<EntryType>()));
|
||||
}
|
||||
|
||||
public enum OSFilter {
|
||||
PRODOS(EntryType.DATA_FORK, EntryType.RESOURCE_FORK, EntryType.REAL_NAME, EntryType.FILE_DATES_INFO,
|
||||
EntryType.PRODOS_FILE_INFO),
|
||||
MACINTOSH(EntryType.DATA_FORK, EntryType.RESOURCE_FORK, EntryType.REAL_NAME, EntryType.COMMENT,
|
||||
EntryType.ICON_BW, EntryType.ICON_COLOR, EntryType.FILE_DATES_INFO, EntryType.FINDER_INFO,
|
||||
EntryType.MACINTOSH_FILE_INFO),
|
||||
MS_DOS(EntryType.DATA_FORK, EntryType.REAL_NAME, EntryType.FILE_DATES_INFO, EntryType.MSDOS_FILE_INFO),
|
||||
AFP(EntryType.DATA_FORK, EntryType.REAL_NAME, EntryType.FILE_DATES_INFO, EntryType.SHORT_NAME,
|
||||
EntryType.AFP_FILE_INFO, EntryType.DIRECTORY_ID);
|
||||
|
||||
public final EntryType[] types;
|
||||
private OSFilter(EntryType... types) {
|
||||
this.types = types;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,7 +15,14 @@ import picocli.CommandLine.Option;
|
|||
commandListHeading = "%nCommands:%n",
|
||||
optionListHeading = "%nOptions:%n",
|
||||
description = "AppleSingle utility",
|
||||
subcommands = { HelpCommand.class, InfoCommand.class, AnalyzeCommand.class, CreateCommand.class, ExtractCommand.class })
|
||||
subcommands = {
|
||||
AnalyzeCommand.class,
|
||||
CreateCommand.class,
|
||||
ExtractCommand.class,
|
||||
FilterCommand.class,
|
||||
HelpCommand.class,
|
||||
InfoCommand.class,
|
||||
})
|
||||
public class Main implements Runnable {
|
||||
@Option(names = "--debug", description = "Dump full stack trackes if an error occurs")
|
||||
private static boolean debugFlag;
|
||||
|
|
Loading…
Reference in New Issue