Archive.IO implementation for BLOB files.
diff --git a/alien/src/wormarc/ArchiveManifest.java b/alien/src/wormarc/ArchiveManifest.java
--- a/alien/src/wormarc/ArchiveManifest.java
+++ b/alien/src/wormarc/ArchiveManifest.java
@@ -169,7 +169,8 @@ public class ArchiveManifest {
outputStream.writeInt(linkCount);
}
- // Placeholder for archive manifest topkey.
+ // ALWAYS write placeholder for archive manifest topkey.
+ // See shouldSkipOne below for the non-sentinel case.
outputStream.write(LinkDigest.NULL_DIGEST.getBytes());
boolean shouldSkipOne = (!insertSentinel);
@@ -187,8 +188,8 @@ public class ArchiveManifest {
return new ByteArrayInputStream(buffer.toByteArray());
}
- // Always closes stream.
- public static ArchiveManifest fromBytes(InputStream rawBytes, LinkDigest chainHeadFixup) throws IOException {
+ public static ArchiveManifest fromBytes(InputStream rawBytes, LinkDigest chainHeadFixup,
+ boolean closeStream) throws IOException {
try {
if (chainHeadFixup == null || chainHeadFixup.isNullDigest()) {
throw new IOException("chainHeadFixup is null or NULL_DIGEST!");
@@ -235,15 +236,21 @@ public class ArchiveManifest {
}
blocks.add(new Block(digests));
}
- inputStream.close();
+ if (closeStream) {
+ inputStream.close();
+ }
rawBytes = null;
return new ArchiveManifest(rootObjects, blocks);
} finally {
- if (rawBytes != null) {
+ if (rawBytes != null && closeStream) {
rawBytes.close();
}
}
}
+ public static ArchiveManifest fromBytes(InputStream rawBytes, LinkDigest chainHeadFixup)
+ throws IOException {
+ return fromBytes(rawBytes, chainHeadFixup, true);
+ }
// Hmmmm... Move?
public static LinkDigest getArchiveManifestDigest(List<Archive.RootObject> rootObjects)
diff --git a/alien/src/wormarc/cli/CLI.java b/alien/src/wormarc/cli/CLI.java
--- a/alien/src/wormarc/cli/CLI.java
+++ b/alien/src/wormarc/cli/CLI.java
@@ -46,6 +46,7 @@ import wormarc.LinkDigest;
import wormarc.ExternalRefs;
import wormarc.RootObjectKind;
+import wormarc.io.FileIO;
import wormarc.io.FreenetIO;
import wormarc.io.FreenetTopKey;
@@ -588,6 +589,46 @@ public class CLI {
}
},
+ new Command("load", true, false, " <file_name>",
+ "Reads an archive version from a blob file.") {
+ public boolean canParse(String[] args) { return args.length == 2; }
+ public void invoke(String[] args, CLICache cache) throws Exception {
+ String fileName = args[1];
+ FileIO io = new FileIO();
+ io.setFile(fileName);
+ sOut.println(String.format("Reading: %s", fileName));
+ Archive archive = Archive.load(io);
+ cache.setName("lastloaded.0");
+ archive.write(cache);
+ cache.saveHead(cache.getName());
+ sOut.println(String.format("Read and switched head to: %s", cache.getName()));
+ sOut.println(String.format("File metadata: %s", io.getMetaData()));
+ }
+ },
+
+ new Command("save", true, false, " <file_name>",
+ "Writes an archive version to a blob file.") {
+ public boolean canParse(String[] args) { return args.length >= 2; }
+ public void invoke(String[] args, CLICache cache) throws Exception {
+ Archive archive = null;
+ try {
+ archive = loadHead(cache);
+ } catch (IOException ioe) {
+ sOut.println("No head found!");
+ return;
+ }
+ String fileName = args[1];
+ FileIO io = new FileIO();
+ io.setFile(fileName);
+ if (args.length > 2) {
+ sOut.println(String.format("Metadata: %s", args[2]));
+ io.setMetaData(args[2]);
+ }
+ sOut.println(String.format("Writing: %s", fileName));
+ archive.write(io);
+ }
+ },
+
// Debugging hack. Remove.
new Command("showargs", false, false, " <arg list>", "print the args passed to it.") {
public boolean canParse(String[] args) { return true; }
diff --git a/alien/src/wormarc/io/BlobIO.java b/alien/src/wormarc/io/BlobIO.java
new file mode 100644
--- /dev/null
+++ b/alien/src/wormarc/io/BlobIO.java
@@ -0,0 +1,219 @@
+/* An Archive.IO implementation to read/write an Archive to/from a BLOB.
+ *
+ * Copyright (C) 2010, 2011 Darrell Karbott
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.0 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Author: djk@isFiaD04zgAgnrEC5XJt1i4IE7AkNPqhBG5bONi6Yks
+ *
+ * This file was developed as component of
+ * "fniki" (a wiki implementation running over Freenet).
+ */
+
+package wormarc.io;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.IOException;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import wormarc.Archive;
+import wormarc.ArchiveManifest; // Hmmm...
+import wormarc.BinaryLinkRep;
+import wormarc.Block;
+import wormarc.HistoryLink;
+import wormarc.HistoryLinkMap;
+import wormarc.IOUtil;
+import wormarc.LinkDataFactory;
+import wormarc.LinkDigest;
+
+public class BlobIO implements Archive.IO {
+ protected final StreamFactory mStreamFactory;
+ protected String mVersion;
+ protected String mMetaData;
+ protected LinkDigest mFixup;
+
+ // REQUIRES: Length must not change.
+ protected final static String VERSION_1 = "BLOB0001";
+
+ // Anything that you can read or write a BLOB to.
+ public interface StreamFactory {
+ InputStream getInputStream() throws IOException;
+ OutputStream getOutputStream() throws IOException;
+ boolean shouldCloseInputStream();
+ boolean shouldCloseOutputStream();
+ }
+
+ protected void readHeader(DataInputStream inputStream) throws IOException {
+ byte[] versionBytes = new byte[8];
+ inputStream.readFully(versionBytes);
+ mVersion = new String(versionBytes, IOUtil.UTF8);
+ if (!mVersion.equals(VERSION_1)) {
+ throw new IOException("Version mismatch or bad data.");
+ }
+
+ int rawLength = inputStream.readUnsignedShort();
+ mMetaData = "";
+ if (rawLength == 0) {
+ return;
+ }
+
+ byte[] raw = new byte[rawLength];
+ inputStream.readFully(raw);
+ mMetaData = new String(raw, IOUtil.UTF8);
+
+ }
+
+ // Doesn't read the history links.
+ protected Archive.ArchiveData readArchiveData(DataInputStream inputStream) throws IOException {
+ byte[] rawFixup = new byte[20];
+ inputStream.readFully(rawFixup);
+ return ArchiveManifest.fromBytes(inputStream, new LinkDigest(rawFixup), false).makeArchiveData();
+ }
+
+ protected void readLinks(HistoryLinkMap linkMap, LinkDataFactory linkFactory,
+ DataInputStream inputStream) throws IOException {
+ while (true) {
+ HistoryLink link = BinaryLinkRep.fromBytes(inputStream, linkFactory);
+ if (link == null) {
+ break;
+ }
+ linkMap.addLink(link);
+ }
+ }
+
+ protected void writeHeader(DataOutputStream outputStream) throws IOException {
+ outputStream.write(VERSION_1.getBytes(IOUtil.UTF8));
+
+ byte[] raw = mMetaData.getBytes(IOUtil.UTF8);
+ if (raw.length > 65535) {
+ throw new IOException("Metadata is too big.");
+ }
+
+ outputStream.writeShort(raw.length); // DCI: test? works for unsigned values right?
+ if (mMetaData.length() == 0) {
+ return;
+ }
+ outputStream.write(raw);
+ }
+
+ protected void writeArchiveData(List<Block> blocks, List<Archive.RootObject> rootObjects,
+ DataOutputStream outputStream)
+ throws IOException {
+
+ LinkDigest digest = ArchiveManifest.getArchiveManifestDigest(rootObjects);
+ if (digest.isNullDigest()) {
+ throw new IOException("Can't write an archive without a non-null ARCHIVE_MANIFEST root object.");
+ }
+
+ // We are using ArchiveManifest to serialize the ArchiveData
+ // purely for expediency. There is some dodgy code
+ // in it which requires the chainHeadFixup, even when we know it. :-(
+ //
+ // HACK: We need to store this for ArchiveManifest.fromBytes().
+ outputStream.write(digest.getBytes());
+
+ ArchiveManifest manifest = new ArchiveManifest(rootObjects, blocks);
+ outputStream.write(IOUtil.readAndClose(manifest.toBytes()));
+ }
+
+ protected Set<LinkDigest> writeBlocks(HistoryLinkMap linkMap, List<Block> blocks,
+ DataOutputStream outputStream) throws IOException {
+ Set<LinkDigest> knownLinks = new HashSet<LinkDigest>();
+ for (Block block : blocks) {
+ for (HistoryLink link : linkMap.getLinks(block)) {
+ if (knownLinks.contains(link)) {
+ continue;
+ }
+ BinaryLinkRep.write(outputStream, link);
+ knownLinks.add(link.mHash);
+ }
+ }
+ return knownLinks;
+ }
+
+ public BlobIO(StreamFactory streamFactory, String metaData) {
+ if (streamFactory == null) {
+ throw new IllegalArgumentException("streamFactory is null");
+ }
+ mStreamFactory = streamFactory;
+
+ if (metaData == null) {
+ throw new IllegalArgumentException("metaData is null");
+ }
+ mMetaData = metaData;
+
+ }
+
+ public String getMetaData() { return mMetaData; }
+ public void setMetaData(String value) {
+ if (value == null) {
+ throw new IllegalArgumentException();
+ }
+ mMetaData = value;
+ }
+
+ ////////////////////////////////////////////////////////////
+ // Archive.IO implementation.
+ ////////////////////////////////////////////////////////////
+
+ public void write(HistoryLinkMap linkMap, List<Block> blocks, List<Archive.RootObject> rootObjects)
+ throws IOException {
+ DataOutputStream outputStream = new DataOutputStream(mStreamFactory.getOutputStream());
+ try {
+ writeHeader(outputStream);
+ writeArchiveData(blocks, rootObjects, outputStream);
+
+ Set<LinkDigest> knownLinks = writeBlocks(linkMap, blocks, outputStream);
+ for (Archive.RootObject obj : rootObjects) {
+ if (!knownLinks.contains(obj.mDigest)) {
+ throw new IOException("Root object not in blocks: " + obj.mDigest);
+ }
+ }
+
+ } finally {
+ if (mStreamFactory.shouldCloseOutputStream()) {
+ outputStream.close();
+ }
+ }
+ }
+
+ public Archive.ArchiveData read(HistoryLinkMap linkMap, LinkDataFactory linkFactory) throws IOException {
+ DataInputStream inputStream = new DataInputStream(mStreamFactory.getInputStream());
+ try {
+ readHeader(inputStream);
+ Archive.ArchiveData archiveData = readArchiveData(inputStream);
+ readLinks(linkMap, linkFactory, inputStream);
+
+ for (Archive.RootObject obj : archiveData.mRootObjects) {
+ if (!linkMap.contains(obj.mDigest)) {
+ throw new IOException("Root object not read from input stream: " + obj.mDigest);
+ }
+ }
+ return archiveData;
+ } finally {
+ if (mStreamFactory.shouldCloseInputStream()) {
+ inputStream.close();
+ }
+ }
+ }
+}
diff --git a/alien/src/wormarc/io/FileIO.java b/alien/src/wormarc/io/FileIO.java
new file mode 100644
--- /dev/null
+++ b/alien/src/wormarc/io/FileIO.java
@@ -0,0 +1,67 @@
+/* An Archive.IO implementation for files.
+ *
+ * Copyright (C) 2010, 2011 Darrell Karbott
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.0 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Author: djk@isFiaD04zgAgnrEC5XJt1i4IE7AkNPqhBG5bONi6Yks
+ *
+ * This file was developed as component of
+ * "fniki" (a wiki implementation running over Freenet).
+ */
+
+package wormarc.io;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+public class FileIO extends BlobIO {
+ private File mFile;
+
+ protected File getFile() throws IOException {
+ if (mFile == null) {
+ throw new IOException("File not set!");
+ }
+ return mFile;
+ }
+
+ private static class StreamFactoryImpl implements StreamFactory {
+ FileIO mFio;
+ public void setTarget(FileIO fio) {
+ mFio = fio;
+ }
+ public InputStream getInputStream() throws IOException { return new FileInputStream(mFio.getFile()); }
+ public OutputStream getOutputStream() throws IOException { return new FileOutputStream(mFio.getFile()); }
+ public boolean shouldCloseInputStream() { return true; }
+ public boolean shouldCloseOutputStream() { return true;}
+ }
+
+ public FileIO() {
+ super(new StreamFactoryImpl(), "");
+ ((StreamFactoryImpl)mStreamFactory).setTarget(this);
+ }
+
+ public void setFile(File file) {
+ mFile = file;
+ }
+
+ public void setFile(String fileName) {
+ mFile = new File(fileName);
+ }
+}
\ No newline at end of file