/* * ==================================================================== * Copyright (c) 2004-2012 TMate Software Ltd. All rights reserved. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at http://svnkit.com/license.html * If newer versions of this license are posted there, you may use a * newer version instead, at your option. * ==================================================================== */ package org.tmatesoft.svn.core.internal.io.fs; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import org.tmatesoft.svn.core.SVNErrorCode; import org.tmatesoft.svn.core.SVNErrorMessage; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.SVNNodeKind; import org.tmatesoft.svn.core.internal.delta.SVNDeltaCombiner; import org.tmatesoft.svn.core.internal.wc.SVNErrorManager; import org.tmatesoft.svn.core.internal.wc.SVNFileUtil; import org.tmatesoft.svn.core.io.ISVNDeltaConsumer; import org.tmatesoft.svn.core.io.SVNRepository; import org.tmatesoft.svn.core.io.diff.SVNDeltaGenerator; import org.tmatesoft.svn.core.io.diff.SVNDiffWindow; import org.tmatesoft.svn.util.SVNDebugLog; import org.tmatesoft.svn.util.SVNLogType; /** * @version 1.3 * @author TMate Software Ltd. */ public class FSOutputStream extends OutputStream implements ISVNDeltaConsumer { public static final int SVN_DELTA_WINDOW_SIZE = 102400; public static final int WRITE_BUFFER_SIZE = 2*SVN_DELTA_WINDOW_SIZE; private boolean isHeaderWritten; private CountingOutputStream myTargetFileOS; private File myTargetFile; private long myDeltaStart; private long myRepSize; private long myRepOffset; private InputStream mySourceStream; private SVNDeltaGenerator myDeltaGenerator; private FSRevisionNode myRevNode; private MessageDigest myMD5Digest; private MessageDigest mySHA1Digest; private FSTransactionRoot myTxnRoot; private long mySourceOffset; private ByteArrayOutputStream myTextBuffer; private boolean myIsClosed; private boolean myIsCompress; private FSWriteLock myTxnLock; private FSOutputStream(FSRevisionNode revNode, CountingOutputStream targetFileOS, File targetFile, InputStream source, long deltaStart, long repSize, long repOffset, FSTransactionRoot txnRoot, boolean compress, FSWriteLock txnLock) throws SVNException { myTxnRoot = txnRoot; myTargetFileOS = targetFileOS; myTargetFile = targetFile; mySourceStream = source; myDeltaStart = deltaStart; myRepSize = repSize; myRepOffset = repOffset; isHeaderWritten = false; myRevNode = revNode; mySourceOffset = 0; myIsClosed = false; myTxnLock = txnLock; myDeltaGenerator = new SVNDeltaGenerator(SVN_DELTA_WINDOW_SIZE); myTextBuffer = new ByteArrayOutputStream(); try { myMD5Digest = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException nsae) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "MD5 implementation not found: {0}", nsae.getLocalizedMessage()); SVNErrorManager.error(err, nsae, SVNLogType.FSFS); } try { mySHA1Digest = MessageDigest.getInstance("SHA1"); } catch (NoSuchAlgorithmException nsae) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, "SHA1 implementation not found: {0}", nsae.getLocalizedMessage()); SVNErrorManager.error(err, nsae, SVNLogType.FSFS); } myIsCompress = compress; } private void reset(FSRevisionNode revNode, CountingOutputStream targetFileOS, File targetFile, InputStream source, long deltaStart, long repSize, long repOffset, FSTransactionRoot txnRoot, FSWriteLock txnLock) { myTxnRoot = txnRoot; myTargetFileOS = targetFileOS; myTargetFile = targetFile; mySourceStream = source; myDeltaStart = deltaStart; myRepSize = repSize; myRepOffset = repOffset; isHeaderWritten = false; myRevNode = revNode; mySourceOffset = 0; myIsClosed = false; myMD5Digest.reset(); mySHA1Digest.reset(); myTextBuffer.reset(); myTxnLock = txnLock; } public static OutputStream createStream(FSRevisionNode revNode, FSTransactionRoot txnRoot, OutputStream dstStream, boolean compress) throws SVNException { if (revNode.getType() != SVNNodeKind.FILE) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_FILE, "Attempted to set textual contents of a *non*-file node"); SVNErrorManager.error(err, SVNLogType.FSFS); } if (!revNode.getId().isTxn()) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_MUTABLE, "Attempted to set textual contents of an immutable node"); SVNErrorManager.error(err, SVNLogType.FSFS); } OutputStream targetOS = null; InputStream sourceStream = null; long offset = -1; long deltaStart = -1; FSWriteLock txnLock = null; try { txnLock = FSWriteLock.getWriteLockForTxn(txnRoot.getTxnID(), txnRoot.getOwner()); txnLock.lock(); File targetFile = txnRoot.getTransactionProtoRevFile(); offset = targetFile.length(); targetOS = SVNFileUtil.openFileForWriting(targetFile, true); CountingOutputStream revWriter = new CountingOutputStream(targetOS, offset); FSRepresentation baseRep = revNode.chooseDeltaBase(txnRoot.getOwner()); sourceStream = FSInputStream.createDeltaStream(new SVNDeltaCombiner(), baseRep, txnRoot.getOwner()); String header; if (baseRep != null) { header = FSRepresentation.REP_DELTA + " " + baseRep.getRevision() + " " + baseRep.getOffset() + " " + baseRep.getSize() + "\n"; } else { header = FSRepresentation.REP_DELTA + "\n"; } revWriter.write(header.getBytes("UTF-8")); deltaStart = revWriter.getPosition(); if (dstStream instanceof FSOutputStream) { FSOutputStream fsOS = (FSOutputStream) dstStream; fsOS.reset(revNode, revWriter, targetFile, sourceStream, deltaStart, 0, offset, txnRoot, txnLock); return dstStream; } return new FSOutputStream(revNode, revWriter, targetFile, sourceStream, deltaStart, 0, offset, txnRoot, compress, txnLock); } catch (IOException ioe) { SVNFileUtil.closeFile(targetOS); SVNFileUtil.closeFile(sourceStream); txnLock.unlock(); FSWriteLock.release(txnLock); SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, ioe.getLocalizedMessage()); SVNErrorManager.error(err, ioe, SVNLogType.FSFS); } catch (SVNException svne) { if (txnLock != null) { txnLock.unlock(); FSWriteLock.release(txnLock); } SVNFileUtil.closeFile(targetOS); SVNFileUtil.closeFile(sourceStream); throw svne; } return null; } public void write(int b) throws IOException { write(new byte[] { (byte) (b & 0xFF) }, 0, 1); } public void write(byte[] b) throws IOException { write(b, 0, b.length); } public void write(byte[] b, int off, int len) throws IOException { myMD5Digest.update(b, off, len); mySHA1Digest.update(b, off, len); myRepSize += len; int toWrite = 0; while (len > 0) { toWrite = len; myTextBuffer.write(b, off, toWrite); if (myTextBuffer.size() >= WRITE_BUFFER_SIZE) { try { ByteArrayInputStream target = new ByteArrayInputStream(myTextBuffer.toByteArray()); myDeltaGenerator.sendDelta(null, mySourceStream, mySourceOffset, target, this, false); } catch (SVNException svne) { throw new IOException(svne.getMessage()); } myTextBuffer.reset(); } off += toWrite; len -= toWrite; } } public void close() throws IOException { if (myIsClosed) { return; } myIsClosed = true; final long truncateToSize[] = new long[] {-1}; try { ByteArrayInputStream target = new ByteArrayInputStream(myTextBuffer.toByteArray()); myDeltaGenerator.sendDelta(null, mySourceStream, mySourceOffset, target, this, false); final FSRepresentation rep = new FSRepresentation(); rep.setOffset(myRepOffset); long offset = myTargetFileOS.getPosition(); rep.setSize(offset - myDeltaStart); rep.setExpandedSize(myRepSize); rep.setTxnId(myRevNode.getId().getTxnID()); String uniqueSuffix = myTxnRoot.getNewTxnNodeId(); String uniquifier = rep.getTxnId() + '/' + uniqueSuffix; rep.setUniquifier(uniquifier); rep.setRevision(SVNRepository.INVALID_REVISION); rep.setMD5HexDigest(SVNFileUtil.toHexDigest(myMD5Digest)); rep.setSHA1HexDigest(SVNFileUtil.toHexDigest(mySHA1Digest)); FSFS fsfs = myTxnRoot.getOwner(); final IFSRepresentationCacheManager reposCacheManager = fsfs.getRepositoryCacheManager(); if (reposCacheManager != null) { try { reposCacheManager.runReadTransaction(new IFSSqlJetTransaction() { public void run() throws SVNException { final FSRepresentation oldRep = reposCacheManager.getRepresentationByHash(rep.getSHA1HexDigest()); if (oldRep != null) { oldRep.setUniquifier(rep.getUniquifier()); oldRep.setMD5HexDigest(rep.getMD5HexDigest()); truncateToSize[0] = myRepOffset; myRevNode.setTextRepresentation(oldRep); } } }); } catch (SVNException e) { // explicitly ignore. SVNDebugLog.getDefaultLog().logError(SVNLogType.FSFS, e); } } if (truncateToSize[0] < 0){ myTargetFileOS.write("ENDREP\n".getBytes("UTF-8")); myRevNode.setTextRepresentation(rep); } myRevNode.setIsFreshTxnRoot(false); fsfs.putTxnRevisionNode(myRevNode.getId(), myRevNode); } catch (SVNException svne) { throw new IOException(svne.getMessage()); } finally { closeStreams(truncateToSize[0]); try { myTxnLock.unlock(); } catch (SVNException e) { // } FSWriteLock.release(myTxnLock); } } public void closeStreams(long truncateToSize) throws IOException { SVNFileUtil.closeFile(myTargetFileOS); SVNFileUtil.closeFile(mySourceStream); if (truncateToSize >= 0) { SVNFileUtil.truncate(myTargetFile, truncateToSize); } } public FSRevisionNode getRevisionNode() { return myRevNode; } public OutputStream textDeltaChunk(String path, SVNDiffWindow diffWindow) throws SVNException { mySourceOffset += diffWindow.getSourceViewLength(); try { diffWindow.writeTo(myTargetFileOS, !isHeaderWritten, myIsCompress); isHeaderWritten = true; } catch (IOException ioe) { SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, ioe.getLocalizedMessage()); SVNErrorManager.error(err, ioe, SVNLogType.FSFS); } return SVNFileUtil.DUMMY_OUT; } public void textDeltaEnd(String path) throws SVNException { } public void applyTextDelta(String path, String baseChecksum) throws SVNException { } }