jgvariant-tool: Add 'ostree summary add-static-delta' command.
Change-Id: I3b318269c4c85b581d6639fe5ec6a14bf2604ad4
diff --git a/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/Main.java b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/Main.java
index fbf5b71..744d902 100644
--- a/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/Main.java
+++ b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/Main.java
@@ -17,7 +17,9 @@
*
* <p>Also provides ways to manipulate OSTree repositories.
*
- * <p>Usage example (dumping the contents of an OSTree summary file):
+ * <h2>Usage Examples</h2>
+ *
+ * <h3>Dumping the contents of an OSTree summary file</h3>
*
* {@snippet lang="sh" :
* $ jgvariant ostree summary read ./jgvariant-ostree/src/test/resources/ostree/summary
@@ -56,6 +58,21 @@
* }
* }
* }
+ *
+ * <h3>Adding a static delta to an OSTree summary file</h3>
+ *
+ * <p>Static delta <code>3...</code> (in hex), between commits <code>1...</code> and <code>2...
+ * </code>:
+ *
+ * {@snippet lang="sh" :
+ * $ jgvariant ostree summary add-static-delta ./jgvariant-ostree/src/test/resources/ostree/summary 3333333333333333333333333333333333333333333333333333333333333333 2222222222222222222222222222222222222222222222222222222222222222 1111111111111111111111111111111111111111111111111111111111111111
+ * }
+ *
+ * <p>Static delta <code>3...</code> (in hex), between the empty commit and <code>2...</code>:
+ *
+ * {@snippet lang="sh" :
+ * $ jgvariant ostree summary add-static-delta ./jgvariant-ostree/src/test/resources/ostree/summary 4444444444444444444444444444444444444444444444444444444444444444 2222222222222222222222222222222222222222222222222222222222222222
+ * }
*/
public final class Main {
static {
diff --git a/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/MainCommand.java b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/MainCommand.java
index fe8211e..2bea23c 100644
--- a/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/MainCommand.java
+++ b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/MainCommand.java
@@ -7,6 +7,10 @@
import static java.util.logging.Level.*;
import eu.mulk.jgvariant.core.Decoder;
+import eu.mulk.jgvariant.core.Signature;
+import eu.mulk.jgvariant.core.Variant;
+import eu.mulk.jgvariant.ostree.ByteString;
+import eu.mulk.jgvariant.ostree.Metadata;
import eu.mulk.jgvariant.ostree.Summary;
import eu.mulk.jgvariant.tool.jsonb.*;
import jakarta.json.bind.Jsonb;
@@ -16,10 +20,15 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
+import java.nio.file.StandardOpenOption;
+import java.text.ParseException;
+import java.util.*;
import java.util.logging.Logger;
+import java.util.stream.IntStream;
import org.jetbrains.annotations.VisibleForTesting;
import picocli.AutoComplete;
import picocli.CommandLine;
@@ -76,10 +85,56 @@
static final class SummaryCommand extends BaseDecoderCommand<Summary> {
@Command(mixinStandardHelpOptions = true)
- void read(@Parameters(paramLabel = "<file>") File file) throws IOException {
+ void read(@Parameters(paramLabel = "<file>", description = "Summary file to read") File file)
+ throws IOException {
read(file, Summary.decoder());
}
+ @Command(name = "add-static-delta", mixinStandardHelpOptions = true)
+ void addStaticDelta(
+ @Parameters(paramLabel = "<file>", description = "Summary file to manipulate.")
+ File summaryFile,
+ @Parameters(paramLabel = "<delta>", description = "Checksum of the static delta (hex).")
+ String delta,
+ @Parameters(paramLabel = "<to>", description = "Commit checksum the delta ends at (hex).")
+ String toCommit,
+ @Parameters(
+ paramLabel = "<from>",
+ arity = "0..1",
+ description = "Commit checksum the delta starts from (hex).")
+ String fromCommit)
+ throws IOException, ParseException {
+ var summaryDecoder = Summary.decoder();
+
+ var summary = decodeFile(summaryFile, summaryDecoder);
+
+ var staticDeltaMapSignature = Signature.parse("a{sv}");
+ var checksumSignature = Signature.parse("ay");
+
+ var metadata = summary.metadata();
+ var metadataFields = new LinkedHashMap<>(metadata.fields());
+ metadataFields.compute(
+ "ostree.static-deltas",
+ (k, v) -> {
+ Map<String, Variant> staticDeltas =
+ v != null
+ ? new LinkedHashMap<>((Map<String, Variant>) v.value())
+ : new LinkedHashMap<>();
+ staticDeltas.put(
+ fromCommit != null ? fromCommit + "-" + toCommit : toCommit,
+ new Variant(checksumSignature, toByteList(ByteString.ofHex(delta).bytes())));
+ return new Variant(staticDeltaMapSignature, staticDeltas);
+ });
+ metadata = new Metadata(metadataFields);
+ summary = new Summary(summary.entries(), metadata);
+
+ encodeFile(summaryFile, summaryDecoder, summary);
+ }
+
+ private List<Byte> toByteList(byte[] bytes) {
+ return IntStream.range(0, bytes.length).mapToObj(i -> bytes[i]).toList();
+ }
+
SummaryCommand() {}
}
@@ -111,10 +166,24 @@
abstract static class BaseDecoderCommand<T> extends BaseCommand {
protected final void read(File file, Decoder<T> decoder) throws IOException {
+ var thing = decodeFile(file, decoder);
+ out().println(jsonb.toJson(thing));
+ }
+
+ protected final T decodeFile(File file, Decoder<T> decoder) throws IOException {
LOG.fine(() -> "Reading file %s".formatted(file));
var fileBytes = ByteBuffer.wrap(Files.readAllBytes(fs().getPath(file.getPath())));
- var thing = decoder.decode(fileBytes);
- out().println(jsonb.toJson(thing));
+ return decoder.decode(fileBytes);
+ }
+
+ @SuppressWarnings("ResultOfMethodCallIgnored")
+ protected final void encodeFile(File file, Decoder<T> decoder, T thing) throws IOException {
+ var thingBytes = decoder.encode(thing);
+
+ LOG.fine(() -> "Writing file %s".formatted(file));
+ try (var out = FileChannel.open(fs().getPath(file.getPath()), StandardOpenOption.WRITE)) {
+ out.write(thingBytes);
+ }
}
}
diff --git a/jgvariant-tool/src/main/java/module-info.java b/jgvariant-tool/src/main/java/module-info.java
index e62808e..6faa226 100644
--- a/jgvariant-tool/src/main/java/module-info.java
+++ b/jgvariant-tool/src/main/java/module-info.java
@@ -11,7 +11,9 @@
*
* <p>The {@link eu.mulk.jgvariant.tool.Main} class defines the entry point of the tool.
*
- * <p>Usage example (dumping the contents of an OSTree summary file):
+ * <h2>Usage Examples</h2>
+ *
+ * <h3>Dumping the contents of an OSTree summary file</h3>
*
* {@snippet lang="sh" :
* $ jgvariant ostree summary read ./jgvariant-ostree/src/test/resources/ostree/summary
@@ -50,6 +52,21 @@
* }
* }
* }
+ *
+ * <h3>Adding a static delta to an OSTree summary file</h3>
+ *
+ * <p>Static delta <code>3...</code> (in hex), between commits <code>1...</code> and <code>2...
+ * </code>:
+ *
+ * {@snippet lang="sh" :
+ * $ jgvariant ostree summary add-static-delta ./jgvariant-ostree/src/test/resources/ostree/summary 3333333333333333333333333333333333333333333333333333333333333333 2222222222222222222222222222222222222222222222222222222222222222 1111111111111111111111111111111111111111111111111111111111111111
+ * }
+ *
+ * <p>Static delta <code>3...</code> (in hex), between the empty commit and <code>2...</code>:
+ *
+ * {@snippet lang="sh" :
+ * $ jgvariant ostree summary add-static-delta ./jgvariant-ostree/src/test/resources/ostree/summary 4444444444444444444444444444444444444444444444444444444444444444 2222222222222222222222222222222222222222222222222222222222222222
+ * }
*/
module eu.mulk.jgvariant.tool {
requires transitive eu.mulk.jgvariant.ostree;