/*
* ====================================================================
* 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.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;
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.diff.SVNDiffWindow;
import org.tmatesoft.svn.util.SVNLogType;
/**
* @version 1.3
* @author TMate Software Ltd.
*/
public class FSInputStream extends InputStream {
private LinkedList myRepStateList = new LinkedList();
private int myChunkIndex;
private boolean isChecksumFinalized;
private String myHexChecksum;
private long myLength;
private long myOffset;
private MessageDigest myDigest;
private ByteBuffer myBuffer;
private SVNDeltaCombiner myCombiner;
private FSInputStream(SVNDeltaCombiner combiner, FSRepresentation representation, FSFS owner) throws SVNException {
myCombiner = combiner;
myChunkIndex = 0;
isChecksumFinalized = false;
myHexChecksum = representation.getMD5HexDigest();
myOffset = 0;
myLength = representation.getExpandedSize();
try {
myDigest = 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 {
buildRepresentationList(representation, myRepStateList, owner);
} catch (SVNException svne) {
/*
* Something terrible has happened while building rep list, need to
* close any files still opened
*/
close();
throw svne;
}
}
public static InputStream createDeltaStream(SVNDeltaCombiner combiner, FSRevisionNode fileNode, FSFS owner) throws SVNException {
if (fileNode == null) {
return SVNFileUtil.DUMMY_IN;
} else if (fileNode.getType() != SVNNodeKind.FILE) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_NOT_FILE, "Attempted to get textual contents of a *non*-file node");
SVNErrorManager.error(err, SVNLogType.FSFS);
}
FSRepresentation representation = fileNode.getTextRepresentation();
if (representation == null) {
return SVNFileUtil.DUMMY_IN;
}
return new FSInputStream(combiner, representation, owner);
}
public static InputStream createDeltaStream(SVNDeltaCombiner combiner, FSRepresentation fileRep, FSFS owner) throws SVNException {
if (fileRep == null) {
return SVNFileUtil.DUMMY_IN;
}
return new FSInputStream(combiner, fileRep, owner);
}
public int read(byte[] buf, int offset, int length) throws IOException {
try {
return readContents(buf, offset, length);
} catch (SVNException svne) {
throw new IOException(svne.getMessage());
}
}
public int read() throws IOException {
byte[] buf = new byte[1];
int r = read(buf, 0, 1);
if (r < 0) {
return -1;
}
return buf[0] & 0xFF;
}
private int readContents(byte[] buf, int offset, int length) throws SVNException {
length = getContents(buf, offset, length);
if (!isChecksumFinalized && length >= 0) {
myDigest.update(buf, offset, length);
myOffset += length;
if (myOffset == myLength) {
isChecksumFinalized = true;
String hexDigest = SVNFileUtil.toHexDigest(myDigest);
if (!myHexChecksum.equals(hexDigest)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Checksum mismatch while reading representation:\n expected: {0}\n actual: {1}", new Object[] {
myHexChecksum, hexDigest
});
SVNErrorManager.error(err, SVNLogType.FSFS);
}
}
}
return length;
}
private int getContents(byte[] buffer, int offset, int length) throws SVNException {
int remaining = length;
int targetPos = offset;
int read = 0;
while (remaining > 0) {
if (myBuffer != null && myBuffer.hasRemaining()) {
int copyLength = Math.min(myBuffer.remaining(), remaining);
/* Actually copy the data. */
myBuffer.get(buffer, targetPos, copyLength);
targetPos += copyLength;
remaining -= copyLength;
read += copyLength;
} else {
FSRepresentationState resultState = (FSRepresentationState) myRepStateList.getFirst();
if (resultState.myOffset == resultState.myEnd) {
if (read == 0) {
read = -1;
}
break;
}
myCombiner.reset();
for (ListIterator states = myRepStateList.listIterator(); states.hasNext();) {
FSRepresentationState curState = (FSRepresentationState) states.next();
while (curState.myChunkIndex < myChunkIndex) {
myCombiner.skipWindow(curState.myFile);
curState.myChunkIndex++;
curState.myOffset = curState.myFile.position();
if (curState.myOffset >= curState.myEnd) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Reading one svndiff window read beyond the end of the representation");
SVNErrorManager.error(err, SVNLogType.FSFS);
}
}
SVNDiffWindow window = myCombiner.readWindow(curState.myFile, curState.myVersion);
ByteBuffer target = myCombiner.addWindow(window);
curState.myChunkIndex++;
curState.myOffset = curState.myFile.position();
if (target != null) {
myBuffer = target;
myChunkIndex++;
break;
}
}
}
}
return read;
}
public void close() {
for (Iterator states = myRepStateList.iterator(); states.hasNext();) {
FSRepresentationState state = (FSRepresentationState) states.next();
if (state.myFile != null) {
state.myFile.close();
}
states.remove();
}
}
private FSRepresentationState buildRepresentationList(FSRepresentation firstRep, LinkedList result, FSFS owner) throws SVNException {
FSFile file = null;
FSRepresentation rep = new FSRepresentation(firstRep);
ByteBuffer buffer = ByteBuffer.allocate(4);
try {
while (true) {
file = owner.openAndSeekRepresentation(rep);
FSRepresentationState repState = readRepresentationLine(file);
repState.myFile = file;
repState.myStart = file.position();
repState.myOffset = repState.myStart;
repState.myEnd = repState.myStart + rep.getSize();
if (!repState.myIsDelta) {
return repState;
}
buffer.clear();
int r = file.read(buffer);
byte[] header = buffer.array();
if (!(header[0] == 'S' && header[1] == 'V' && header[2] == 'N' && r == 4)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Malformed svndiff data in representation");
SVNErrorManager.error(err, SVNLogType.FSFS);
}
repState.myVersion = header[3];
repState.myChunkIndex = 0;
repState.myOffset += 4;
/*
* Push this rep onto the list. If it's self-compressed, we're
* done.
*/
result.addLast(repState);
if (repState.myIsDeltaVsEmpty) {
return null;
}
rep.setRevision(repState.myBaseRevision);
rep.setOffset(repState.myBaseOffset);
rep.setSize(repState.myBaseLength);
rep.setTxnId(null);
}
} catch (IOException ioe) {
file.close();
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, ioe.getLocalizedMessage());
SVNErrorManager.error(err, ioe, SVNLogType.FSFS);
} catch (SVNException svne) {
file.close();
throw svne;
}
return null;
}
public static FSRepresentationState readRepresentationLine(FSFile file) throws SVNException {
try {
String line = file.readLine(160);
FSRepresentationState repState = new FSRepresentationState();
repState.myIsDelta = false;
if (FSRepresentation.REP_PLAIN.equals(line)) {
return repState;
}
if (FSRepresentation.REP_DELTA.equals(line)) {
/* This is a delta against the empty stream. */
repState.myIsDelta = true;
repState.myIsDeltaVsEmpty = true;
return repState;
}
repState.myIsDelta = true;
repState.myIsDeltaVsEmpty = false;
/* We have hopefully a DELTA vs. a non-empty base revision. */
int delimiterInd = line.indexOf(' ');
if (delimiterInd == -1) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Malformed representation header");
SVNErrorManager.error(err, SVNLogType.FSFS);
}
String header = line.substring(0, delimiterInd);
if (!FSRepresentation.REP_DELTA.equals(header)) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Malformed representation header");
SVNErrorManager.error(err, SVNLogType.FSFS);
}
line = line.substring(delimiterInd + 1);
try {
delimiterInd = line.indexOf(' ');
if (delimiterInd == -1) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Malformed representation header");
SVNErrorManager.error(err, SVNLogType.FSFS);
}
String baseRevision = line.substring(0, delimiterInd);
repState.myBaseRevision = Long.parseLong(baseRevision);
line = line.substring(delimiterInd + 1);
delimiterInd = line.indexOf(' ');
if (delimiterInd == -1) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Malformed representation header");
SVNErrorManager.error(err, SVNLogType.FSFS);
}
String baseOffset = line.substring(0, delimiterInd);
repState.myBaseOffset = Long.parseLong(baseOffset);
line = line.substring(delimiterInd + 1);
repState.myBaseLength = Long.parseLong(line);
} catch (NumberFormatException nfe) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_CORRUPT, "Malformed representation header");
SVNErrorManager.error(err, SVNLogType.FSFS);
}
return repState;
} catch (SVNException svne) {
file.close();
throw svne;
}
}
public static class FSRepresentationState {
FSFile myFile;
/* The starting offset for the raw svndiff/plaintext data minus header. */
long myStart;
/* The current offset into the file. */
long myOffset;
/* The end offset of the raw data. */
long myEnd;
/* If a delta, what svndiff version? */
int myVersion;
int myChunkIndex;
boolean myIsDelta;
boolean myIsDeltaVsEmpty;
long myBaseRevision;
long myBaseOffset;
long myBaseLength;
}
}