/* * Part of the CCNx Java Library. * * Copyright (C) 2008, 2009 Palo Alto Research Center, Inc. * * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License version 2.1 * as published by the Free Software Foundation. * 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 * Lesser General Public License for more details. You should have received * a copy of the GNU Lesser General Public License along with this library; * if not, write to the Free Software Foundation, Inc., 51 Franklin Street, * Fifth Floor, Boston, MA 02110-1301 USA. */ package org.ccnx.ccn.io; import java.io.IOException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; import java.util.logging.Level; import org.ccnx.ccn.CCNHandle; import org.ccnx.ccn.impl.CCNFlowControl; import org.ccnx.ccn.impl.security.crypto.ContentKeys; import org.ccnx.ccn.impl.support.Log; import org.ccnx.ccn.io.content.ContentEncodingException; import org.ccnx.ccn.io.content.Header; import org.ccnx.ccn.io.content.Header.HeaderObject; import org.ccnx.ccn.profiles.SegmentationProfile; import org.ccnx.ccn.profiles.metadata.MetadataProfile; import org.ccnx.ccn.protocol.ContentName; import org.ccnx.ccn.protocol.KeyLocator; import org.ccnx.ccn.protocol.PublisherPublicKeyDigest; import org.ccnx.ccn.protocol.SignedInfo.ContentType; /** * A versioned output stream that adds a header containing file-level metadata * to every stream it outputs (see Header for contents). Reading this * content with CCNFileInputStream will allow retrieval of both the * content (including automatic retrieval of the latest version, if desired), * and the header. */ public class CCNFileOutputStream extends CCNVersionedOutputStream { public CCNFileOutputStream(ContentName name, CCNHandle handle) throws IOException { this(name, (PublisherPublicKeyDigest)null, handle); } public CCNFileOutputStream(ContentName name, PublisherPublicKeyDigest publisher, CCNHandle handle) throws IOException { this(name, null, publisher, null, null, handle); } public CCNFileOutputStream(ContentName name, ContentKeys keys, CCNHandle handle) throws IOException { this(name, null, null, null, keys, handle); } public CCNFileOutputStream(ContentName name, KeyLocator locator, PublisherPublicKeyDigest publisher, ContentKeys keys, CCNHandle handle) throws IOException { this(name, locator, publisher, null, keys, handle); } public CCNFileOutputStream(ContentName name, KeyLocator locator, PublisherPublicKeyDigest publisher, ContentType type, ContentKeys keys, CCNHandle handle) throws IOException { super(name, locator, publisher, type, keys, handle); } protected CCNFileOutputStream(ContentName name, KeyLocator locator, PublisherPublicKeyDigest publisher, ContentType type, ContentKeys keys, CCNFlowControl flowControl) throws IOException { super(name, locator, publisher, type, keys, flowControl); } /** * Writes the header to the network. * @throws IOException */ protected void writeHeader() throws ContentEncodingException, IOException { // What do we put in the header if we have multiple merkle trees? putHeader(_baseName, lengthWritten(), getBlockSize(), _dh.digest(), null); } /** * Subclasses that want to do something other than write a header at the end * should override this, not close(), because CCNOutputStream.close() currently * calls waitForPutDrain, and we don't want to call that till after we've put the header. * * When we can, we might want to write the header earlier. Here we wait * till we know how many bytes are in the file. * @throws ContentEncodingException * @throws IOException * @throws InterruptedException * @throws NoSuchAlgorithmException * @throws SignatureException * @throws InvalidKeyException */ @Override protected void closeNetworkData() throws ContentEncodingException, IOException, InvalidKeyException, SignatureException, NoSuchAlgorithmException, InterruptedException { super.closeNetworkData(); writeHeader(); } /** * Actually put the header blocks (versioned, though that isn't necessary) onto the wire. */ protected void putHeader( ContentName name, long contentLength, int blockSize, byte [] contentDigest, byte [] contentTreeAuthenticator) throws ContentEncodingException, IOException { ContentName headerName = MetadataProfile.headerName(name); // Really want to query the segmenter about the last block we wrote. Header headerData = new Header(SegmentationProfile.baseSegment(), this._baseNameIndex, blockSize, contentLength, contentDigest, contentTreeAuthenticator); if (Log.isLoggable(Log.FAC_IO, Level.FINEST)) Log.finest(Log.FAC_IO, "HEADER: Writing header, starting segment " + headerData.start() + " count " + headerData.count() + " length " + headerData.length()); // DKS TODO -- deal with header encryption, making sure it has same publisher as // rest of file via the segmenter // The segmenter contains the flow controller. Should do the right thing whether this // is a raw stream or a repo stream. It should also already have the keys. Could just share // the segmenter. For now, use our own. HeaderObject header = new HeaderObject(headerName, headerData, this._publisher, this._locator, this.getSegmenter().getFlowControl()); header.save(); } }