/*******************************************************************************
* Copyright (c) 2006-2013, Cloudsmith Inc.
* The code, documentation and other materials contained herein have been
* licensed under the Eclipse Public License - v 1.0 by the copyright holder
* listed above, as the Initial Contributor under such license. The text of
* such license is available at www.eclipse.org.
******************************************************************************/
package org.eclipse.buckminster.cvspkg.internal;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.Stack;
import org.eclipse.buckminster.core.helpers.FileUtils;
import org.eclipse.buckminster.cvspkg.Messages;
import org.eclipse.buckminster.runtime.IOUtils;
import org.eclipse.buckminster.runtime.MonitorUtils;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.osgi.util.NLS;
import org.eclipse.team.internal.ccvs.core.CVSException;
import org.eclipse.team.internal.ccvs.core.ICVSFile;
import org.eclipse.team.internal.ccvs.core.ICVSFolder;
import org.eclipse.team.internal.ccvs.core.ICVSResourceVisitor;
import org.eclipse.team.internal.ccvs.core.syncinfo.FolderSyncInfo;
import org.eclipse.team.internal.ccvs.core.syncinfo.MutableResourceSyncInfo;
import org.eclipse.team.internal.ccvs.core.syncinfo.ResourceSyncInfo;
/**
* @author Thomas Hallgren
*/
@SuppressWarnings("restriction")
public class FileSystemCopier implements ICVSResourceVisitor {
/**
* An instance of this class is created whenever we enter a folder and
* pushed on a stack. It manages all the sync information for the elements
* of that folder.
*/
private static class FolderInfo {
private final IPath path;
private final ArrayList<byte[]> entries;
private final ICVSFolder folder;
private boolean isCreated;
FolderInfo(ICVSFolder folder, IPath path) throws CVSException {
this.folder = folder;
this.entries = new ArrayList<byte[]>();
this.path = path;
}
void addEntry(byte[] entry) {
entries.add(entry);
}
void assertCreated() throws CVSException {
if (!isCreated) {
createDirectory(path);
isCreated = true;
}
}
byte[] getFolderSyncBytes() throws CVSException {
return folder.getSyncInfo().getBytes();
}
IPath getPath() {
return path;
}
boolean isEmpty() {
return entries.isEmpty();
}
boolean isPrefixOf(IPath test) {
return path.isPrefixOf(test);
}
/**
* Writes the CVS/Root, CVS/Repository, CVS/Tag, CVS/Entries, and
* CVS/Entries.static files to the specified folder using the data
* contained in the specified FolderSyncInfo instance.
*/
void writeSync() throws CVSException {
this.assertCreated();
IPath cvsFolder = createDirectory(path.append(CVS_DIRNAME));
// format file contents
//
int idx = entries.size();
String[] entryArray = new String[idx];
while (--idx >= 0)
entryArray[idx] = new String(entries.get(idx));
// write Entries
//
writeLines(cvsFolder, ENTRIES, entryArray);
// write CVS/Root
//
FolderSyncInfo info = folder.getFolderSyncInfo();
writeLines(cvsFolder, ROOT, info.getRoot());
// write CVS/Repository
//
writeLines(cvsFolder, REPOSITORY, info.getRepository());
if (info.getTag() != null)
//
// write CVS/Tag
//
writeLines(cvsFolder, TAG, info.getTag().toEntryLineFormat(false));
if (info.getIsStatic())
//
// write CVS/Entries.Static
// the existence of the file is all that matters
//
writeLines(cvsFolder, STATIC, ""); //$NON-NLS-1$
}
}
/** the famous CVS meta directory name */
public static final String CVS_DIRNAME = "CVS"; //$NON-NLS-1$
// CVS meta files located in the CVS subdirectory
//
public static final String REPOSITORY = "Repository"; //$NON-NLS-1$
public static final String ROOT = "Root"; //$NON-NLS-1$
public static final String STATIC = "Entries.Static"; //$NON-NLS-1$
public static final String TAG = "Tag"; //$NON-NLS-1$
public static final String ENTRIES = "Entries"; //$NON-NLS-1$
private static IPath createDirectory(IPath parentFolder) throws CVSException {
File dir = parentFolder.toFile();
if (!dir.mkdirs() && !dir.exists())
throw new CVSException(NLS.bind(Messages.unable_to_create_directory_0, dir));
return parentFolder;
}
private static void writeLines(IPath parentFolder, String metaFile, String... lines) throws CVSException {
PrintWriter out = null;
try {
out = new PrintWriter(parentFolder.append(metaFile).toFile());
for (String line : lines)
out.println(line);
} catch (FileNotFoundException e) {
throw CVSException.wrapException(e);
} finally {
IOUtils.close(out);
}
}
private final IPath fsRoot;
private final ICVSFolder cvsRoot;
private final IProgressMonitor monitor;
// This stack helps us keep track of what folder is current and when we
// leave
// that folder (the path of the top FolderInfo is no longer a prefix of the
// current element).
//
private final Stack<FolderInfo> currentPath = new Stack<FolderInfo>();
public FileSystemCopier(ICVSFolder cvsRoot, IPath fsRoot, IProgressMonitor monitor) throws CVSException {
this.cvsRoot = cvsRoot;
this.fsRoot = fsRoot;
this.monitor = monitor;
this.monitor.beginTask(null, IProgressMonitor.UNKNOWN);
}
public void done() throws CVSException {
while (!currentPath.isEmpty())
this.visitFolderEnd();
monitor.done();
}
@Override
public void visitFile(ICVSFile file) throws CVSException {
this.checkFolderEnd(file.getParent());
FolderInfo fi = currentPath.peek();
fi.assertCreated();
OutputStream out = null;
InputStream in = null;
File osFile = fi.getPath().append(file.getName()).toFile();
try {
out = new FileOutputStream(osFile);
in = file.getContents();
FileUtils.copyFile(in, out, MonitorUtils.subMonitor(monitor, 10));
} catch (IOException e) {
throw CVSException.wrapException(e);
} finally {
IOUtils.close(in);
IOUtils.close(out);
}
ResourceSyncInfo syncInfo = file.getSyncInfo();
Date timestamp = syncInfo.getTimeStamp();
if (timestamp != null)
osFile.setLastModified(timestamp.getTime());
else {
MutableResourceSyncInfo mrsi = syncInfo.cloneMutable();
mrsi.setTimeStamp(new Date(osFile.lastModified()));
syncInfo = mrsi;
}
fi.addEntry(syncInfo.getBytes());
}
@Override
public void visitFolder(ICVSFolder folder) throws CVSException {
this.checkFolderEnd(folder);
currentPath.push(new FolderInfo(folder, fsRoot.append(folder.getRelativePath(cvsRoot))));
}
private void checkFolderEnd(ICVSFolder folder) throws CVSException {
if (!currentPath.isEmpty()) {
IPath fullPath = fsRoot.append(folder.getRelativePath(cvsRoot));
while (!currentPath.peek().isPrefixOf(fullPath))
this.visitFolderEnd();
}
}
private void visitFolderEnd() throws CVSException {
FolderInfo info = currentPath.pop();
if (info.isEmpty())
return;
info.writeSync();
MonitorUtils.worked(monitor, 5);
if (!currentPath.isEmpty())
currentPath.peek().addEntry(info.getFolderSyncBytes());
}
}