/*
* ====================================================================
* 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.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.tmatesoft.svn.core.ISVNCanceller;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.internal.io.fs.revprop.SVNFSFSPackedRevProps;
import org.tmatesoft.svn.core.internal.io.fs.revprop.SVNFSFSPackedRevPropsManifest;
import org.tmatesoft.svn.core.internal.wc.SVNErrorManager;
import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
import org.tmatesoft.svn.core.wc.ISVNEventHandler;
import org.tmatesoft.svn.core.wc.admin.ISVNAdminEventHandler;
import org.tmatesoft.svn.core.wc.admin.SVNAdminEvent;
import org.tmatesoft.svn.core.wc.admin.SVNAdminEventAction;
import org.tmatesoft.svn.util.SVNLogType;
/**
* @version 1.3
* @author TMate Software Ltd.
*/
public class FSPacker {
private ISVNCanceller myCanceller;
private ISVNAdminEventHandler myNotifyHandler;
public FSPacker(ISVNAdminEventHandler notifyHandler) {
myCanceller = notifyHandler == null ? ISVNCanceller.NULL : notifyHandler;
myNotifyHandler = notifyHandler;
}
public void pack(FSFS fsfs) throws SVNException {
FSWriteLock writeLock = FSWriteLock.getWriteLockForDB(fsfs);
synchronized (writeLock) {
try {
writeLock.lock();
packImpl(fsfs);
} finally {
writeLock.unlock();
FSWriteLock.release(writeLock);
}
}
}
private void packImpl(FSFS fsfs) throws SVNException {
int format = fsfs.getDBFormat();
if (format < FSFS.MIN_PACKED_FORMAT) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.FS_UNSUPPORTED_FORMAT, "FS format too old to pack, please upgrade.");
SVNErrorManager.error(err, SVNLogType.FSFS);
}
long maxFilesPerDirectory = fsfs.getMaxFilesPerDirectory();
if (maxFilesPerDirectory <= 0) {
return;
}
long minUnpackedRev = fsfs.getMinUnpackedRev();
long youngestRev = fsfs.getYoungestRevision();
long completedShards = (youngestRev + 1) / maxFilesPerDirectory;
long minUnpackedRevProp = 0;
boolean packRevisionProperties = fsfs.getDBFormat() >= FSFS.MIN_PACKED_REVPROP_FORMAT;
if (packRevisionProperties)
{
minUnpackedRevProp =fsfs.getMinUnpackedRevProp();
}
if (minUnpackedRev == (completedShards * maxFilesPerDirectory) &&
minUnpackedRevProp == (completedShards * maxFilesPerDirectory)
) {
return;
}
for (long i = minUnpackedRev / maxFilesPerDirectory; i < completedShards; i++) {
myCanceller.checkCancelled();
packShard(fsfs, i, packRevisionProperties);
}
}
private void packShard(FSFS fsfs, long shard, boolean packRevisionProperties) throws SVNException {
File revShardPath = new File(fsfs.getDBRevsDir(), String.valueOf(shard));
File revpropShardPath = new File(fsfs.getRevisionPropertiesRoot(), String.valueOf(shard));
packRevShard(fsfs, shard, revShardPath);
if (packRevisionProperties) {
myCanceller.checkCancelled();
packRevPropShard(fsfs, shard, revpropShardPath, (long)(0.9 * fsfs.getRevPropPackSize()));
}
File finalPath = fsfs.getMinUnpackedRevFile();
File tmpFile = SVNFileUtil.createUniqueFile(fsfs.getDBRoot(), "tempfile", ".tmp", false);
String line = String.valueOf((shard + 1) * fsfs.getMaxFilesPerDirectory()) + '\n';
SVNFileUtil.writeToFile(tmpFile, line, "UTF-8");
SVNFileUtil.rename(tmpFile, finalPath);
SVNFileUtil.deleteAll(revShardPath, true, myCanceller);
if (packRevisionProperties) {
deleteRevPropShard(revpropShardPath, shard, fsfs.getMaxFilesPerDirectory());
}
firePackEvent(shard, false);
}
private void deleteRevPropShard(File revpropShardPath, long shard, long maxFilesPerDirectory) throws SVNException {
if (shard == 0) {
for (int i = 1; i < maxFilesPerDirectory; i++) {
if (myCanceller != null) {
myCanceller.checkCancelled();
}
final File path = new File(revpropShardPath, String.valueOf(i));
SVNFileUtil.deleteFile(path);
}
} else {
SVNFileUtil.deleteAll(revpropShardPath, true, myCanceller);
}
}
private void packRevShard(FSFS fsfs, long shard, File shardPath) throws SVNException {
File packDir = fsfs.getPackDir(shard);
File packFile = fsfs.getPackFile(shard);
File manifestFile = fsfs.getManifestFile(shard);
firePackEvent(shard, true);
SVNFileUtil.deleteAll(packDir, false, myCanceller);
long startRev = shard * fsfs.getMaxFilesPerDirectory();
long endRev = (shard + 1) * fsfs.getMaxFilesPerDirectory() - 1;
long nextOffset = 0;
OutputStream packFileOS = null;
OutputStream manifestFileOS = null;
try {
packFileOS = SVNFileUtil.openFileForWriting(packFile);
manifestFileOS = SVNFileUtil.openFileForWriting(manifestFile);
for (long rev = startRev; rev <= endRev; rev++) {
File path = new File(shardPath, String.valueOf(rev));
String line = String.valueOf(nextOffset) + '\n';
manifestFileOS.write(line.getBytes("UTF-8"));
nextOffset += path.length();
InputStream revIS = null;
try {
revIS = SVNFileUtil.openFileForReading(path);
FSRepositoryUtil.copy(revIS, packFileOS, myCanceller);
} finally {
SVNFileUtil.closeFile(revIS);
}
}
} catch (IOException ioe) {
SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.IO_ERROR, ioe.getMessage());
SVNErrorManager.error(err, ioe, SVNLogType.FSFS);
} finally {
SVNFileUtil.closeFile(packFileOS);
SVNFileUtil.closeFile(manifestFileOS);
}
}
private void firePackEvent(long shard, boolean start) throws SVNException {
if (myNotifyHandler != null) {
SVNAdminEvent event = new SVNAdminEvent(start ? SVNAdminEventAction.PACK_START : SVNAdminEventAction.PACK_END, shard);
myNotifyHandler.handleAdminEvent(event, ISVNEventHandler.UNKNOWN);
}
}
private void packRevPropShard(FSFS fsfs, long shard, File shardPath, long maxPackSize) throws SVNException {
File packPath = new File(fsfs.getRevisionPropertiesRoot(), String.valueOf(shard) + FSFS.PACK_EXT);
firePackEvent(shard, true);
long startRev = shard * fsfs.getMaxFilesPerDirectory();
long endRev = (shard + 1) * fsfs.getMaxFilesPerDirectory() - 1;
if (startRev == 0) {
startRev++;
}
long totalSize = 2 * SVNFSFSPackedRevProps.INT64_BUFFER_SIZE;
boolean packIsEmpty = true;
String packName = null;
final SVNFSFSPackedRevPropsManifest.Builder manifestBuilder = new SVNFSFSPackedRevPropsManifest.Builder();
for (long rev = startRev; rev <= endRev; rev++) {
final File path = new File(shardPath, String.valueOf(rev));
final long size = path.length();
if (!packIsEmpty && totalSize + SVNFSFSPackedRevProps.INT64_BUFFER_SIZE + size > maxPackSize) {
copyRevProps(packName, packPath, shardPath, startRev, rev-1, fsfs.isCompressPackedRevprops());
totalSize = 2 * SVNFSFSPackedRevProps.INT64_BUFFER_SIZE;
startRev = rev;
packIsEmpty = true;
}
if (packIsEmpty) {
packName = rev + ".0";
}
manifestBuilder.addPackName(packName);
packIsEmpty = false;
totalSize += SVNFSFSPackedRevProps.INT64_BUFFER_SIZE + size;
}
if (!packIsEmpty) {
copyRevProps(packName, packPath, shardPath, startRev, endRev /*=rev - 1*/, fsfs.isCompressPackedRevprops());
}
final SVNFSFSPackedRevPropsManifest manifest = manifestBuilder.build();
SVNFileUtil.writeToFile(new File(packPath, FSFS.MANIFEST_FILE), manifest.asString(), "UTF-8");
}
private void copyRevProps(String packName, File packPath, File shardPath, long startRev, long endRev, boolean compressPackedRevprops) throws SVNException {
final SVNFSFSPackedRevProps.Builder packedRevPropsBuilder = new SVNFSFSPackedRevProps.Builder();
packedRevPropsBuilder.setFirstRevision(startRev);
for (long rev = startRev; rev <= endRev; rev++) {
final File revPropFile = new File(shardPath, String.valueOf(rev));
final byte[] content = SVNFileUtil.readFully(revPropFile);
packedRevPropsBuilder.addByteArrayEntry(content);
}
final SVNFSFSPackedRevProps packedRevProps = packedRevPropsBuilder.build();
final File packFile = new File(packPath, packName);
packedRevProps.writeToFile(packFile, compressPackedRevprops);
}
}