/*
* ====================================================================
* 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.wc;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.Map;
import org.tmatesoft.svn.core.SVNCommitInfo;
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.SVNProperties;
import org.tmatesoft.svn.core.SVNPropertyValue;
import org.tmatesoft.svn.core.internal.delta.SVNDeltaCombiner;
import org.tmatesoft.svn.core.internal.io.fs.CountingOutputStream;
import org.tmatesoft.svn.core.internal.io.fs.FSFS;
import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryUtil;
import org.tmatesoft.svn.core.internal.io.fs.FSRevisionNode;
import org.tmatesoft.svn.core.internal.io.fs.FSRevisionRoot;
import org.tmatesoft.svn.core.internal.io.fs.FSRoot;
import org.tmatesoft.svn.core.internal.util.SVNHashMap;
import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
import org.tmatesoft.svn.core.internal.wc.admin.SVNChecksumInputStream;
import org.tmatesoft.svn.core.io.ISVNDeltaConsumer;
import org.tmatesoft.svn.core.io.ISVNEditor;
import org.tmatesoft.svn.core.io.diff.SVNDeltaGenerator;
import org.tmatesoft.svn.core.io.diff.SVNDiffWindow;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.util.SVNDebugLog;
import org.tmatesoft.svn.util.SVNLogType;
/**
* @version 1.3
* @author TMate Software Ltd.
*/
public class SVNDumpEditor implements ISVNEditor {
private FSRoot myRoot;
private FSFS myFSFS;
private long myTargetRevision;
private long myOldestDumpedRevision;
private String myRootPath;
private OutputStream myDumpStream;
private boolean myUseDeltas;
private boolean myIsVerify;
private DirectoryInfo myCurrentDirInfo;
private SVNDeltaCombiner myDeltaCombiner;
private SVNDeltaGenerator myDeltaGenerator;
public SVNDumpEditor(FSFS fsfs, FSRoot root, long toRevision, long oldestDumpedRevision, String rootPath, OutputStream dumpStream,
boolean useDeltas, boolean isVerify) {
myRoot = root;
myFSFS = fsfs;
myTargetRevision = toRevision;
myOldestDumpedRevision = oldestDumpedRevision;
myRootPath = rootPath;
myDumpStream = dumpStream;
myUseDeltas = useDeltas;
myIsVerify = isVerify;
}
public void reset(FSFS fsfs, FSRoot root, long toRevision, long oldestDumpedRevision, String rootPath, OutputStream dumpStream,
boolean useDeltas, boolean isVerify) {
myRoot = root;
myFSFS = fsfs;
myTargetRevision = toRevision;
myOldestDumpedRevision = oldestDumpedRevision;
myRootPath = rootPath;
myDumpStream = dumpStream;
myUseDeltas = useDeltas;
myIsVerify = isVerify;
myCurrentDirInfo = null;
}
public void abortEdit() throws SVNException {
}
public void absentDir(String path) throws SVNException {
}
public void absentFile(String path) throws SVNException {
}
public void addDir(String path, String copyFromPath, long copyFromRevision) throws SVNException {
DirectoryInfo parent = myCurrentDirInfo;
myCurrentDirInfo = createDirectoryInfo(path, copyFromPath, copyFromRevision, parent);
boolean isDeleted = parent.myDeletedEntries.containsKey(path);
boolean isCopy = copyFromPath != null && SVNRevision.isValidRevisionNumber(copyFromRevision);
dumpNode(path, SVNNodeKind.DIR, isDeleted ? SVNAdminHelper.NODE_ACTION_REPLACE : SVNAdminHelper.NODE_ACTION_ADD, isCopy, isCopy ? copyFromPath : null, isCopy ? copyFromRevision : -1);
if (isDeleted) {
parent.myDeletedEntries.remove(path);
}
myCurrentDirInfo.myIsWrittenOut = true;
}
public void addFile(String path, String copyFromPath, long copyFromRevision) throws SVNException {
boolean isCopy = copyFromPath != null && SVNRevision.isValidRevisionNumber(copyFromRevision);
boolean isDeleted = myCurrentDirInfo.myDeletedEntries.containsKey(path);
dumpNode(path, SVNNodeKind.FILE, isDeleted ? SVNAdminHelper.NODE_ACTION_REPLACE : SVNAdminHelper.NODE_ACTION_ADD, isCopy, isCopy ? copyFromPath : null, isCopy ? copyFromRevision : -1);
if (isDeleted) {
myCurrentDirInfo.myDeletedEntries.remove(path);
}
}
public void changeDirProperty(String name, SVNPropertyValue value) throws SVNException {
if (!myCurrentDirInfo.myIsWrittenOut) {
dumpNode(myCurrentDirInfo.myFullPath, SVNNodeKind.DIR, SVNAdminHelper.NODE_ACTION_CHANGE, false,
myCurrentDirInfo.myComparePath, myCurrentDirInfo.myCompareRevision);
myCurrentDirInfo.myIsWrittenOut = true;
}
}
public void changeFileProperty(String path, String name, SVNPropertyValue value) throws SVNException {
}
public void closeDir() throws SVNException {
if (myIsVerify) {
FSRevisionNode node = myRoot.getRevisionNode(myCurrentDirInfo.myFullPath);
Map entries = node.getDirEntries(myFSFS);
for (Iterator entriesIter = entries.keySet().iterator(); entriesIter.hasNext();) {
String entryName = (String) entriesIter.next();
String entryPath = SVNPathUtil.append(myCurrentDirInfo.myFullPath, entryName);
SVNNodeKind kind = myRoot.checkNodeKind(entryPath);
FSRevisionNode entryNode = myRoot.getRevisionNode(entryPath);
if (kind == SVNNodeKind.DIR) {
entryNode.getDirEntries(myFSFS);
} else if (kind == SVNNodeKind.FILE) {
entryNode.getFileLength();
} else {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.NODE_UNEXPECTED_KIND,
"Unexpected node kind {0} for ''{1}''", new Object[] { kind, entryPath });
SVNErrorManager.error(err, SVNLogType.FSFS);
}
}
}
for (Iterator entries = myCurrentDirInfo.myDeletedEntries.keySet().iterator(); entries.hasNext();) {
String path = (String) entries.next();
dumpNode(path, SVNNodeKind.UNKNOWN, SVNAdminHelper.NODE_ACTION_DELETE, false, null, -1);
}
myCurrentDirInfo = myCurrentDirInfo.myParentInfo;
}
public SVNCommitInfo closeEdit() throws SVNException {
return null;
}
public void closeFile(String path, String textChecksum) throws SVNException {
}
public void deleteEntry(String path, long revision) throws SVNException {
myCurrentDirInfo.myDeletedEntries.put(path, path);
}
public void openDir(String path, long revision) throws SVNException {
DirectoryInfo parent = myCurrentDirInfo;
String cmpPath = null;
long cmpRev = -1;
if (parent != null && parent.myComparePath != null && SVNRevision.isValidRevisionNumber(parent.myCompareRevision)) {
cmpPath = SVNPathUtil.append(parent.myComparePath, SVNPathUtil.tail(path));
cmpRev = parent.myCompareRevision;
}
myCurrentDirInfo = createDirectoryInfo(path, cmpPath, cmpRev, parent);
}
public void openFile(String path, long revision) throws SVNException {
String cmpPath = null;
long cmpRev = -1;
if (myCurrentDirInfo != null && myCurrentDirInfo.myComparePath != null && SVNRevision.isValidRevisionNumber(myCurrentDirInfo.myCompareRevision)) {
cmpPath = SVNPathUtil.append(myCurrentDirInfo.myComparePath, SVNPathUtil.tail(path));
cmpRev = myCurrentDirInfo.myCompareRevision;
}
dumpNode(path, SVNNodeKind.FILE, SVNAdminHelper.NODE_ACTION_CHANGE, false, cmpPath, cmpRev);
}
public void openRoot(long revision) throws SVNException {
myCurrentDirInfo = createDirectoryInfo(null, null, -1, null);
}
public void targetRevision(long revision) throws SVNException {
}
public void applyTextDelta(String path, String baseChecksum) throws SVNException {
}
public OutputStream textDeltaChunk(String path, SVNDiffWindow diffWindow) throws SVNException {
return null;
}
public void textDeltaEnd(String path) throws SVNException {
}
private void dumpNode(String path, SVNNodeKind kind, int nodeAction, boolean isCopy, String cmpPath, long cmpRev) throws SVNException {
File tmpFile = null;
try {
writeDumpData(SVNAdminHelper.DUMPFILE_NODE_PATH + ": " + (path.startsWith("/") ? path.substring(1) : path) + "\n");
if (kind == SVNNodeKind.FILE) {
writeDumpData(SVNAdminHelper.DUMPFILE_NODE_KIND + ": file\n");
} else if (kind == SVNNodeKind.DIR) {
writeDumpData(SVNAdminHelper.DUMPFILE_NODE_KIND + ": dir\n");
}
if (cmpPath != null) {
cmpPath = cmpPath.startsWith("/") ? cmpPath.substring(1) : cmpPath;
}
String comparePath = path;
long compareRevision = myTargetRevision - 1;
if (cmpPath != null && SVNRevision.isValidRevisionNumber(cmpRev)) {
comparePath = cmpPath;
compareRevision = cmpRev;
}
comparePath = SVNPathUtil.canonicalizePath(comparePath);
comparePath = SVNPathUtil.getAbsolutePath(comparePath);
FSRevisionRoot compareRoot = null;
boolean mustDumpProps = false;
boolean mustDumpText = false;
String canonicalPath = SVNPathUtil.getAbsolutePath(SVNPathUtil.canonicalizePath(path));
switch(nodeAction) {
case SVNAdminHelper.NODE_ACTION_CHANGE:
writeDumpData(SVNAdminHelper.DUMPFILE_NODE_ACTION + ": change\n");
compareRoot = myFSFS.createRevisionRoot(compareRevision);
mustDumpProps = FSRepositoryUtil.arePropertiesChanged(compareRoot, comparePath, myRoot, canonicalPath);
if (kind == SVNNodeKind.FILE) {
mustDumpText = FSRepositoryUtil.areFileContentsChanged(compareRoot, comparePath, myRoot, canonicalPath);
}
break;
case SVNAdminHelper.NODE_ACTION_REPLACE:
if (!isCopy) {
writeDumpData(SVNAdminHelper.DUMPFILE_NODE_ACTION + ": replace\n");
if (kind == SVNNodeKind.FILE) {
mustDumpText = true;
}
mustDumpProps = true;
} else {
writeDumpData(SVNAdminHelper.DUMPFILE_NODE_ACTION + ": delete\n\n");
dumpNode(path, kind, SVNAdminHelper.NODE_ACTION_ADD, isCopy, comparePath, compareRevision);
mustDumpText = false;
mustDumpProps = false;
}
break;
case SVNAdminHelper.NODE_ACTION_DELETE:
writeDumpData(SVNAdminHelper.DUMPFILE_NODE_ACTION + ": delete\n");
mustDumpText = false;
mustDumpProps = false;
break;
case SVNAdminHelper.NODE_ACTION_ADD:
writeDumpData(SVNAdminHelper.DUMPFILE_NODE_ACTION + ": add\n");
if (!isCopy) {
if (kind == SVNNodeKind.FILE) {
mustDumpText = true;
}
mustDumpProps = true;
} else {
if (!myIsVerify && cmpRev < myOldestDumpedRevision) {
SVNDebugLog.getDefaultLog().logFine(SVNLogType.FSFS,
"WARNING: Referencing data in revision " + cmpRev +
", which is older than the oldest\nWARNING: dumped revision (" +
myOldestDumpedRevision +
"). Loading this dump into an empty repository\nWARNING: will fail.\n");
}
writeDumpData(SVNAdminHelper.DUMPFILE_NODE_COPYFROM_REVISION + ": " + cmpRev + "\n");
writeDumpData(SVNAdminHelper.DUMPFILE_NODE_COPYFROM_PATH + ": " + cmpPath + "\n");
compareRoot = myFSFS.createRevisionRoot(compareRevision);
mustDumpProps = FSRepositoryUtil.arePropertiesChanged(compareRoot, comparePath, myRoot,
canonicalPath);
if (kind == SVNNodeKind.FILE) {
mustDumpText = FSRepositoryUtil.areFileContentsChanged(compareRoot, comparePath,
myRoot, canonicalPath);
FSRevisionNode revNode = compareRoot.getRevisionNode(comparePath);
String checkSum = revNode.getFileMD5Checksum();
if (checkSum != null && checkSum.length() > 0) {
writeDumpData(SVNAdminHelper.DUMPFILE_TEXT_COPY_SOURCE_MD5 + ": " + checkSum + "\n");
}
checkSum = revNode.getFileSHA1Checksum();
if (checkSum != null && checkSum.length() > 0) {
writeDumpData(SVNAdminHelper.DUMPFILE_TEXT_COPY_SOURCE_SHA1 + ": " + checkSum + "\n");
}
}
}
break;
}
if (!mustDumpProps && !mustDumpText) {
writeDumpData("\n\n");
return;
}
long contentLength = 0;
String propContents = null;
if (mustDumpProps) {
FSRevisionNode node = myRoot.getRevisionNode(canonicalPath);
SVNProperties props = node.getProperties(myFSFS);
SVNProperties oldProps = null;
if (myUseDeltas && compareRoot != null) {
FSRevisionNode cmpNode = compareRoot.getRevisionNode(comparePath);
oldProps = cmpNode.getProperties(myFSFS);
writeDumpData(SVNAdminHelper.DUMPFILE_PROP_DELTA + ": true\n");
}
ByteArrayOutputStream encodedProps = new ByteArrayOutputStream();
SVNAdminHelper.writeProperties(props, oldProps, encodedProps);
propContents = new String(encodedProps.toByteArray(), "UTF-8");
contentLength += propContents.length();
writeDumpData(SVNAdminHelper.DUMPFILE_PROP_CONTENT_LENGTH + ": " + propContents.length() + "\n");
}
if (mustDumpText && kind == SVNNodeKind.FILE) {
long txtLength = 0;
FSRevisionNode node = myRoot.getRevisionNode(canonicalPath);
if (myUseDeltas) {
tmpFile = SVNFileUtil.createTempFile("dump", ".tmp");
InputStream sourceStream = null;
InputStream targetStream = null;
OutputStream tmpStream = null;
SVNDeltaCombiner deltaCombiner = getDeltaCombiner();
SVNDeltaGenerator deltaGenerator = getDeltaGenerator();
try {
if (compareRoot != null && comparePath != null) {
sourceStream = compareRoot.getFileStreamForPath(deltaCombiner, comparePath);
} else {
sourceStream = SVNFileUtil.DUMMY_IN;
}
targetStream = myRoot.getFileStreamForPath(deltaCombiner, canonicalPath);
tmpStream = SVNFileUtil.openFileForWriting(tmpFile);
final CountingOutputStream countingStream = new CountingOutputStream(tmpStream, 0);
ISVNDeltaConsumer consumer = new ISVNDeltaConsumer() {
private boolean isHeaderWritten = false;
public OutputStream textDeltaChunk(String path, SVNDiffWindow diffWindow) throws SVNException {
try {
if (diffWindow != null) {
diffWindow.writeTo(countingStream, !isHeaderWritten, false);
} else {
SVNDiffWindow.EMPTY.writeTo(countingStream, !isHeaderWritten, false);
}
} catch (IOException ioe) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, ioe.getLocalizedMessage());
SVNErrorManager.error(err, ioe, SVNLogType.FSFS);
}
isHeaderWritten = true;
return SVNFileUtil.DUMMY_OUT;
}
public void applyTextDelta(String path, String baseChecksum) throws SVNException {
}
public void textDeltaEnd(String path) throws SVNException {
}
};
deltaGenerator.sendDelta(null, sourceStream, 0, targetStream, consumer, false);
txtLength = countingStream.getPosition();
if (compareRoot != null) {
FSRevisionNode revNode = compareRoot.getRevisionNode(comparePath);
String hexDigest = revNode.getFileMD5Checksum();
if (hexDigest != null && hexDigest.length() > 0) {
writeDumpData(SVNAdminHelper.DUMPFILE_TEXT_DELTA_BASE_MD5 + ": " + hexDigest + "\n");
}
hexDigest = revNode.getFileSHA1Checksum();
if (hexDigest == null) {
hexDigest = computeSHA1Checksum(compareRoot, comparePath);
}
if (hexDigest != null && hexDigest.length() > 0) {
writeDumpData(SVNAdminHelper.DUMPFILE_TEXT_DELTA_BASE_SHA1 + ": " + hexDigest + "\n");
}
}
} finally {
SVNFileUtil.closeFile(sourceStream);
SVNFileUtil.closeFile(targetStream);
SVNFileUtil.closeFile(tmpStream);
}
writeDumpData(SVNAdminHelper.DUMPFILE_TEXT_DELTA + ": true\n");
} else {
txtLength = node.getFileLength();
}
contentLength += txtLength;
writeDumpData(SVNAdminHelper.DUMPFILE_TEXT_CONTENT_LENGTH + ": " + txtLength + "\n");
String checksum = node.getFileMD5Checksum();
if (checksum != null && checksum.length() > 0) {
writeDumpData(SVNAdminHelper.DUMPFILE_TEXT_CONTENT_MD5 + ": " + checksum + "\n");
}
checksum = node.getFileSHA1Checksum();
if (checksum == null) {
checksum = computeSHA1Checksum(myRoot, canonicalPath);
}
if (checksum != null && checksum.length() > 0) {
writeDumpData(SVNAdminHelper.DUMPFILE_TEXT_CONTENT_SHA1 + ": " + checksum + "\n");
}
}
writeDumpData(SVNAdminHelper.DUMPFILE_CONTENT_LENGTH + ": " + contentLength + "\n\n");
if (mustDumpProps) {
writeDumpData(propContents);
}
if (mustDumpText && kind == SVNNodeKind.FILE) {
InputStream source = null;
try {
if (tmpFile != null) {
source = SVNFileUtil.openFileForReading(tmpFile, SVNLogType.WC);
} else {
source = myRoot.getFileStreamForPath(getDeltaCombiner(), canonicalPath);
}
//TODO: provide canceller?
FSRepositoryUtil.copy(source, myDumpStream, null);
} finally {
SVNFileUtil.closeFile(source);
}
}
writeDumpData("\n\n");
} catch (IOException ioe) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, ioe.getLocalizedMessage());
SVNErrorManager.error(err, ioe, SVNLogType.FSFS);
} finally {
SVNFileUtil.deleteFile(tmpFile);
}
}
private String computeSHA1Checksum(FSRoot revision, String filePath) throws SVNException {
InputStream is = revision.getFileStreamForPath(getDeltaCombiner(), filePath);
SVNChecksumInputStream checksum = null;
try {
checksum = new SVNChecksumInputStream(is, "SHA1");
} finally {
SVNFileUtil.closeFile(checksum);
}
return checksum != null ? checksum.getDigest() : null;
}
private SVNDeltaGenerator getDeltaGenerator() {
if (myDeltaGenerator == null) {
myDeltaGenerator = new SVNDeltaGenerator();
}
return myDeltaGenerator;
}
private SVNDeltaCombiner getDeltaCombiner() {
if (myDeltaCombiner == null) {
myDeltaCombiner = new SVNDeltaCombiner();
} else {
myDeltaCombiner.reset();
}
return myDeltaCombiner;
}
private DirectoryInfo createDirectoryInfo(String path, String copyFromPath, long copyFromRev, DirectoryInfo parent) {
String fullPath = null;
if (parent != null) {
fullPath = SVNPathUtil.getAbsolutePath(SVNPathUtil.append(myRootPath, path));
} else {
fullPath = myRootPath;
}
String cmpPath = null;
if (copyFromPath != null) {
cmpPath = copyFromPath.startsWith("/") ? copyFromPath.substring(1) : copyFromPath;
}
return new DirectoryInfo(fullPath, cmpPath, copyFromRev, parent);
}
private void writeDumpData(String data) throws IOException {
myDumpStream.write(data.getBytes("UTF-8"));
}
private class DirectoryInfo {
String myFullPath;
String myComparePath;
long myCompareRevision;
boolean myIsWrittenOut;
Map myDeletedEntries;
DirectoryInfo myParentInfo;
public DirectoryInfo(String path, String cmpPath, long cmpRev, DirectoryInfo parent) {
myFullPath = path;
myParentInfo = parent;
myComparePath = cmpPath;
myCompareRevision = cmpRev;
myDeletedEntries = new SVNHashMap();
myIsWrittenOut = false;
}
}
}