/* Copyright 2004-2014 Jim Voris
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.qumasoft.server;
import com.qumasoft.qvcslib.CompareFilesEditHeader;
import com.qumasoft.qvcslib.Compressor;
import com.qumasoft.qvcslib.LabelInfo;
import com.qumasoft.qvcslib.QVCSConstants;
import com.qumasoft.qvcslib.QVCSException;
import com.qumasoft.qvcslib.RevisionHeader;
import com.qumasoft.qvcslib.RevisionInformation;
import com.qumasoft.qvcslib.Utility;
import com.qumasoft.qvcslib.commandargs.CheckInCommandArgs;
import com.qumasoft.qvcslib.commandargs.GetRevisionCommandArgs;
import com.qumasoft.qvcslib.commandargs.LockRevisionCommandArgs;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.util.Date;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Abstract logfile operation. Abstract base class for server operations. Code common across different operations belongs here.
* @author Jim Voris
*/
public abstract class AbstractLogFileOperation {
// Create our logger object
private static final Logger LOGGER = Logger.getLogger("com.qumasoft.server");
private static final int FILECOPY_BUFFER_SIZE = 524_288;
private final Object[] args;
private final LogFileImpl logfileImpl;
/**
* Creates a new instance of AbstractLogFileOperation.
* @param a the arguments.
* @param impl the logfile implementation for this operation.
*/
public AbstractLogFileOperation(Object[] a, LogFileImpl impl) {
this.args = a;
this.logfileImpl = impl;
}
LogFileImpl getLogFileImpl() {
return this.logfileImpl;
}
Object[] getArgs() {
return this.args;
}
/**
* Perform the operation.
* @return true if things worked; false otherwise.
* @throws QVCSException if things went wrong.
*/
public abstract boolean execute() throws QVCSException;
static void copyFromOneOpenFileToAnotherOpenFile(RandomAccessFile sourceStream, RandomAccessFile outputStream, long numberOfBytesToCopyFromSource) throws IOException {
// Assume that both streams are positioned where they need to be positioned.
if (numberOfBytesToCopyFromSource > 0L) {
byte[] fileCopyBuffer = new byte[FILECOPY_BUFFER_SIZE];
while (true) {
// Figure out how much to tranfer with this read/write
long lChunk;
if (((numberOfBytesToCopyFromSource / FILECOPY_BUFFER_SIZE) > 0)) {
lChunk = FILECOPY_BUFFER_SIZE;
} else {
lChunk = (numberOfBytesToCopyFromSource % FILECOPY_BUFFER_SIZE);
}
// See if there is any more to xfer
if (lChunk <= 0L) {
break;
}
int lRead = sourceStream.read(fileCopyBuffer, 0, (int) lChunk);
if (lRead == lChunk) {
outputStream.write(fileCopyBuffer, 0, (int) lChunk);
} else {
throw new IOException("Failed to read " + lChunk + " bytes.");
}
numberOfBytesToCopyFromSource -= lRead;
}
}
}
static void copyFromOneOpenFileToAnotherOpenFile(InputStream sourceStream, RandomAccessFile outputStream, long numberOfBytesToCopyFromSource) throws IOException {
// Assume that both streams are positioned where they need to be positioned.
if (numberOfBytesToCopyFromSource > 0L) {
byte[] fileCopyBuffer = new byte[FILECOPY_BUFFER_SIZE];
while (true) {
// Figure out how much to tranfer with this read/write
long lChunk;
if (((numberOfBytesToCopyFromSource / FILECOPY_BUFFER_SIZE) > 0)) {
lChunk = FILECOPY_BUFFER_SIZE;
} else {
lChunk = (numberOfBytesToCopyFromSource % FILECOPY_BUFFER_SIZE);
}
// See if there is any more to xfer
if (lChunk <= 0L) {
break;
}
int lRead = sourceStream.read(fileCopyBuffer, 0, (int) lChunk);
if (lRead == lChunk) {
outputStream.write(fileCopyBuffer, 0, (int) lChunk);
}
numberOfBytesToCopyFromSource -= lRead;
}
}
}
static void copyFromOneOpenFileToAnotherOpenFile(InputStream sourceStream, OutputStream outputStream, long numberOfBytesToCopyFromSource) throws IOException {
// Assume that both streams are positioned where they need to be positioned.
if (numberOfBytesToCopyFromSource > 0L) {
byte[] fileCopyBuffer = new byte[FILECOPY_BUFFER_SIZE];
while (true) {
// Figure out how much to tranfer with this read/write
long lChunk;
if (((numberOfBytesToCopyFromSource / FILECOPY_BUFFER_SIZE) > 0)) {
lChunk = FILECOPY_BUFFER_SIZE;
} else {
lChunk = (numberOfBytesToCopyFromSource % FILECOPY_BUFFER_SIZE);
}
// See if there is any more to xfer
if (lChunk <= 0L) {
break;
}
int lRead = sourceStream.read(fileCopyBuffer, 0, (int) lChunk);
if (lRead == lChunk) {
outputStream.write(fileCopyBuffer, 0, (int) lChunk);
}
numberOfBytesToCopyFromSource -= lRead;
}
}
}
protected boolean getRevision(String userName, AtomicReference<String> mutableRevisionString, String outputFilename) throws QVCSException {
GetRevisionCommandArgs commandArgs = new GetRevisionCommandArgs();
commandArgs.setRevisionString(mutableRevisionString.get());
commandArgs.setShortWorkfileName(logfileImpl.getShortWorkfileName());
commandArgs.setOutputFileName(outputFilename);
commandArgs.setUserName(userName);
return logfileImpl.getRevision(commandArgs, outputFilename);
}
/**
* Used by the checkInRevision operation. This returns the name of the tempfile that we get the revision into.
* @param userName the user name.
* @param revisionString the revision string.
* @return the full file name of the temp file that contains the fetched revision associated with the given revision string.
* @throws com.qumasoft.qvcslib.QVCSException
*/
protected String getRevisionToTempfile(String userName, String revisionString) throws QVCSException {
File tempFileForExistingRevision = null;
try {
tempFileForExistingRevision = File.createTempFile("QVCS", ".tmp");
} catch (java.io.IOException e) {
LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e));
throw new QVCSException("Failed to create QVCS temp file: " + e.getMessage());
}
String tempFileNameForExistingRevision = tempFileForExistingRevision.getAbsolutePath();
AtomicReference<String> mutableRevisionString = new AtomicReference<>(revisionString);
if (!getRevision(userName, mutableRevisionString, tempFileNameForExistingRevision)) {
throw new QVCSException("Failed to get revision " + mutableRevisionString.get() + " into QVCS temp file: " + tempFileNameForExistingRevision);
} else {
LOGGER.log(Level.FINEST, "Fetched revision: " + revisionString + " into temporary file: " + tempFileNameForExistingRevision);
}
return tempFileNameForExistingRevision;
}
protected int getCompressedRevisionSizeForWorkfile(File workfile, Compressor compressor) {
return getCompressedRevisionSize(workfile, 0, compressor);
}
protected int getCompressedRevisionSizeForDelta(File tempFileForCompareResults, Compressor compressor) {
return getCompressedRevisionSize(tempFileForCompareResults, CompareFilesEditHeader.getEditHeaderSize(), compressor);
}
protected int getCompressedRevisionSize(File inputFile, int skipBeginningByteCount, Compressor compressor) {
int returnedLength = (int) inputFile.length() - skipBeginningByteCount;
java.io.FileInputStream inputStream = null;
try {
inputStream = new java.io.FileInputStream(inputFile);
compressor.setUncompressedBuffer(new byte[returnedLength]);
inputStream.skip(skipBeginningByteCount);
Utility.readDataFromStream(compressor.getUncompressedBuffer(), inputStream);
if (logfileImpl.getLogFileHeaderInfo().getLogFileHeader().attributes().getIsCompression()) {
if (compressor.compress(compressor.getUncompressedBuffer())) {
returnedLength = compressor.getCompressedBuffer().length;
LOGGER.log(Level.FINEST, "Compressed from: " + compressor.getUncompressedBuffer().length + " to: " + returnedLength);
}
}
} catch (IOException e) {
// Really should log a problem here.
LOGGER.log(Level.WARNING, e.getMessage());
LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e));
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
LOGGER.log(Level.INFO, Utility.expandStackTraceToString(e));
}
}
}
return returnedLength;
}
protected String getRevisionStringFromLabel(final java.lang.String labelString) {
String revisionStringForLabel = null;
// This is a 'get by label' request. Find the label, if we can.
if ((labelString != null) && (logfileImpl != null)) {
LabelInfo[] labelInfo = logfileImpl.getLogFileHeaderInfo().getLabelInfo();
if (labelInfo != null) {
for (LabelInfo labelInfo1 : labelInfo) {
if (labelString.equals(labelInfo1.getLabelString())) {
// If it is a floating label, we have to figure out the
// revision string...
if (labelInfo1.isFloatingLabel()) {
RevisionInformation revisionInformation = logfileImpl.getRevisionInformation();
int revisionCount = logfileImpl.getRevisionCount();
for (int j = 0; j < revisionCount; j++) {
RevisionHeader revHeader = revisionInformation.getRevisionHeader(j);
if (revHeader.getDepth() == labelInfo1.getDepth()) {
if (revHeader.isTip()) {
String labelRevisionString = labelInfo1.getLabelRevisionString();
String revisionString = revHeader.getRevisionString();
if (revisionString.startsWith(labelRevisionString)) {
revisionStringForLabel = revisionString;
break;
}
}
}
}
} else {
revisionStringForLabel = labelInfo1.getLabelRevisionString();
}
break;
}
}
}
}
return revisionStringForLabel;
}
protected void addTipRevision(String userName, String operationName, String checkinComment, Date checkinDate, String appendedPathWorkfileName, String shortWorkfileName,
RevisionHeader revisionHeader) throws QVCSException {
// Get this tip revision to a temp file that we'll use for the checkin
// operation.
String tempFileName = getRevisionToTempfile(userName, revisionHeader.getRevisionString());
// If lock-checking is enabled, I have to lock this tip revision before
// I can create a new tip revision.
if (logfileImpl.getLogFileHeaderInfo().getLogFileHeader().attributes().getIsCheckLock()) {
// The command args
LockRevisionCommandArgs currentCommandArgs = new LockRevisionCommandArgs();
currentCommandArgs.setRevisionString(revisionHeader.getRevisionString());
currentCommandArgs.setUserName(userName);
currentCommandArgs.setFullWorkfileName(appendedPathWorkfileName);
currentCommandArgs.setShortWorkfileName(shortWorkfileName);
currentCommandArgs.setOutputFileName(tempFileName);
if (!logfileImpl.lockRevision(currentCommandArgs)) {
throw new QVCSException("Failed to lock revision " + revisionHeader.getRevisionString() + " for " + operationName + " operation.");
}
}
// Checkin the new revision for this tip.
CheckInCommandArgs checkInCommandArgs = new CheckInCommandArgs();
checkInCommandArgs.setUserName(userName);
checkInCommandArgs.setLockedRevisionString(revisionHeader.getRevisionString());
checkInCommandArgs.setCheckInComment(checkinComment);
checkInCommandArgs.setInputfileTimeStamp(checkinDate);
checkInCommandArgs.setFullWorkfileName(appendedPathWorkfileName);
checkInCommandArgs.setShortWorkfileName(shortWorkfileName);
// Set flags;
checkInCommandArgs.setLockFlag(false);
checkInCommandArgs.setForceBranchFlag(false);
checkInCommandArgs.setApplyLabelFlag(false);
checkInCommandArgs.setFloatLabelFlag(false);
checkInCommandArgs.setReuseLabelFlag(false);
checkInCommandArgs.setCreateNewRevisionIfEqual(true);
checkInCommandArgs.setNoExpandKeywordsFlag(true);
checkInCommandArgs.setProtectWorkfileFlag(false);
// Set some other values
checkInCommandArgs.setLabel(null);
// And add the new tip revision.
if (!logfileImpl.checkInRevision(checkInCommandArgs, tempFileName, false)) {
throw new QVCSException("Failed to add revision for " + operationName + " operation.");
}
}
// <editor-fold>
protected void addTipRevisionForTranslucentBranch(String userName, String operationName, String checkinComment, Date checkinDate, String appendedPathWorkfileName,
String shortWorkfileName,
RevisionHeader revisionHeader, final String branchLabel) throws QVCSException {
// </editor-fold>
// Get this tip revision to a temp file that we'll use for the checkin
// operation.
String tempFileName = getRevisionToTempfile(userName, revisionHeader.getRevisionString());
// Checkin the new revision for this tip.
CheckInCommandArgs checkInCommandArgs = new CheckInCommandArgs();
if (logfileImpl.getLogfileInfo().getLogFileHeaderInfo().hasLabel(branchLabel)) {
checkInCommandArgs.setForceBranchFlag(false);
checkInCommandArgs.setReuseLabelFlag(true);
} else {
checkInCommandArgs.setForceBranchFlag(true);
}
checkInCommandArgs.setUserName(userName);
checkInCommandArgs.setLockedRevisionString(revisionHeader.getRevisionString());
checkInCommandArgs.setCheckInComment(checkinComment);
checkInCommandArgs.setInputfileTimeStamp(checkinDate);
checkInCommandArgs.setFullWorkfileName(appendedPathWorkfileName);
checkInCommandArgs.setShortWorkfileName(shortWorkfileName);
// Set flags;
checkInCommandArgs.setLockFlag(false);
checkInCommandArgs.setApplyLabelFlag(true);
checkInCommandArgs.setCreateNewRevisionIfEqual(true);
checkInCommandArgs.setNoExpandKeywordsFlag(true);
checkInCommandArgs.setProtectWorkfileFlag(false);
// Set some other values
checkInCommandArgs.setLabel(branchLabel);
// And add the new tip revision.
if (!logfileImpl.checkInRevision(checkInCommandArgs, tempFileName, true)) {
throw new QVCSException("Failed to add revision for " + operationName + " operation.");
}
}
/**
* Is the given revision the tip revision of a translucent or opaque branch?
*
* @param revisionHeader the revision in question.
* @return true if the revision is the tip revision on either a translucent or opaque branch; false for all other revisions.
*/
protected boolean isRevisionTipRevisionOfTranslucentOrOpaqueBranch(RevisionHeader revisionHeader) {
boolean retVal = false;
if (revisionHeader.isTip() && revisionHeader.getDepth() > 0) {
String revisionString = revisionHeader.getRevisionString();
LabelInfo[] labelInfo = logfileImpl.getLogFileHeaderInfo().getLabelInfo();
if (labelInfo != null) {
for (LabelInfo labelInfo1 : labelInfo) {
String labelRevisionString = labelInfo1.getLabelRevisionString();
if (labelRevisionString.equals(revisionString)) {
// This label is associated with this revision.... is it a label for a translucent branch?
if (labelInfo1.getLabelString().startsWith(QVCSConstants.QVCS_TRANSLUCENT_BRANCH_LABEL)) {
retVal = true;
break;
} else if (labelInfo1.getLabelString().startsWith(QVCSConstants.QVCS_OPAQUE_BRANCH_LABEL)) {
// This label is associated with this revision.... is it a label for an opaque branch?
retVal = true;
break;
}
}
}
}
}
return retVal;
}
}