/*******************************************************************************
* This file is protected by Copyright.
* Please refer to the COPYRIGHT file distributed with this source distribution.
*
* This file is part of REDHAWK IDE.
*
* All rights reserved. This program and the accompanying materials are made available under
* the terms of the Eclipse Public License v1.0 which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package gov.redhawk.ide.sdr.ui.export;
import gov.redhawk.ide.sdr.ui.SdrUiPlugin;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileInfo;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubMonitor;
/**
* This class is used to perform "export" operations - essentially mkdir's and copy's on a target EFS file system.
* <p />
* FUTURE WORK: Although the class currently takes an {@link IPath} to find its target root, this could be changed to
* a URL, allowing the class to generically target any EFS that Eclipse knows about. The class uses {@link IResource}s
* as sources for copy operations.
* @since 3.1
*/
public class FileStoreExporter implements IScaExporter {
private final IPath rootPath;
private final URI rootURI;
/**
* File/directory exclude patterns (see {@link #FileStoreExporter(IPath, List)}).
*/
private final List<Pattern> excludePatterns;
/**
* Creates a default exporter that ignores files beginning with a dot (.)
*
* @param rootPath The root path of the destination export location
*/
public FileStoreExporter(final IPath rootPath) {
this(rootPath, new ArrayList<Pattern>());
this.excludePatterns.add(Pattern.compile("^\\.")); // Ignore files beginning with a dot
}
/**
* Creates an exporter that ignores the specified file patterns.
*
* @param rootPath The root path of the destination export location
* @param excludePatterns A {@link List} of regular expressions. Files matching any regular expression will
* <b>not</b> be exported when calling {@link #write(IResource, IPath, IProgressMonitor)}. Regular expressions
* are matched greedily, i.e. any subset of the file name can match (so use ^ or $ if you need them).
*/
public FileStoreExporter(final IPath rootPath, final List<Pattern> excludePatterns) {
super();
this.rootPath = rootPath;
if (rootPath != null && rootPath.toFile() != null) {
this.rootURI = rootPath.toFile().toURI();
} else {
this.rootURI = null;
}
this.excludePatterns = new ArrayList<Pattern>(excludePatterns);
}
@Override
public void finished() throws IOException {
// PASS - nothing needed
}
private static final int FETCH_WORK = 5;
private static final int COPY_WORK = 95;
@Override
public void write(final IResource resource, final IPath destinationPath, final IProgressMonitor monitor) throws IOException, CoreException {
final SubMonitor progress = SubMonitor.convert(monitor, FileStoreExporter.FETCH_WORK + FileStoreExporter.FETCH_WORK + FileStoreExporter.COPY_WORK);
// Calculate destination URI
final URI destURI = this.rootURI.resolve(destinationPath.toString());
// Create the root directory we'll be copying into
if (destinationPath.removeLastSegments(1).segmentCount() > 0) {
final IPath dirPath = destinationPath.removeLastSegments(1);
mkdir(dirPath, progress.newChild(FileStoreExporter.FETCH_WORK));
}
progress.setWorkRemaining(FileStoreExporter.FETCH_WORK + FileStoreExporter.COPY_WORK);
// Get the IFileStores and perform a recursive copy
final IFileStore srcFileStore = EFS.getStore(resource.getLocationURI());
final IFileInfo srcFileInfo = srcFileStore.fetchInfo(EFS.NONE, progress.newChild(FileStoreExporter.FETCH_WORK));
final IFileStore destFileStore = EFS.getStore(destURI);
recursiveCopy(srcFileStore, srcFileInfo, destFileStore, progress.newChild(FileStoreExporter.COPY_WORK));
}
/**
* Recursively copies files/directories from one file store to another, excluding anything matching the
* {@link #excludePatterns exclude patterns}.
*
* @param srcFileStore The source file store
* @param srcFileInfo The source file info (from {@link IFileStore#fetchInfo(int, IProgressMonitor)})
* @param destFileStore The destination file store
* @param monitor The progress monitor to use for reporting progress to the user. It is the caller's responsibility
* to call done() on the given monitor. Accepts null, indicating that no progress should be
* reported and that the operation cannot be canceled.
* @throws CoreException Copying fails (see {@link IFileStore#copy(IFileStore, int, IProgressMonitor)} for reasons)
*/
private void recursiveCopy(final IFileStore srcFileStore, final IFileInfo srcFileInfo, final IFileStore destFileStore, final IProgressMonitor monitor)
throws CoreException {
if (monitor != null && monitor.isCanceled()) {
return;
}
// We do nothing if the filename matches an exclude pattern
final String sourceName = srcFileInfo.getName();
for (final Pattern excludePattern : this.excludePatterns) {
if (excludePattern.matcher(sourceName).find()) {
return;
}
}
// Copy logic (directory / individual file)
if (srcFileInfo.isDirectory()) {
final SubMonitor progress = SubMonitor.convert(monitor, FileStoreExporter.FETCH_WORK + FileStoreExporter.COPY_WORK);
// Fetch children
final IFileStore[] srcChildFileStores = srcFileStore.childStores(0, progress.newChild(FileStoreExporter.FETCH_WORK));
// Copy the directory itself - disallow recursion; we'll do that ourselves
final SubMonitor copyProgress = progress.newChild(FileStoreExporter.COPY_WORK).setWorkRemaining(
FileStoreExporter.COPY_WORK + srcChildFileStores.length * (FileStoreExporter.FETCH_WORK + FileStoreExporter.COPY_WORK));
srcFileStore.copy(destFileStore, EFS.OVERWRITE | EFS.SHALLOW, copyProgress.newChild(FileStoreExporter.COPY_WORK));
for (final IFileStore srcChildFileStore : srcChildFileStores) {
final IFileInfo srcChildFileInfo = srcChildFileStore.fetchInfo(EFS.NONE, copyProgress.newChild(FileStoreExporter.FETCH_WORK));
recursiveCopy(srcChildFileStore, srcChildFileInfo, destFileStore.getChild(srcChildFileInfo.getName()),
copyProgress.newChild(FileStoreExporter.COPY_WORK));
}
} else {
final SubMonitor progress = SubMonitor.convert(monitor, FileStoreExporter.COPY_WORK);
srcFileStore.copy(destFileStore, EFS.OVERWRITE, progress.newChild(FileStoreExporter.COPY_WORK));
}
}
@Override
public void mkdir(final IPath destinationPath, final IProgressMonitor monitor) throws IOException, CoreException {
final URI destURI = this.rootURI.resolve(destinationPath.toString());
EFS.getStore(destURI).mkdir(EFS.NONE, monitor);
}
@Override
public IPath getExportLocation() {
return this.rootPath;
}
@Override
public String toString() {
return getClass().getName() + " [" + this.rootURI.toString() + "]";
}
/**
* All for friends to add custom exclude patterns
* @param pattern The pattern to include for exclusion
*/
public void addExcludePattern(final Pattern pattern) {
if (pattern != null) {
this.excludePatterns.add(pattern);
}
}
/**
* Manually create a symlink between the target (just name) and the source (full path + name)
* @param target Just the name of the target file
* @param source The full path and name of the source file is needed
* @return boolean Whether or not the process completed correctly
* @throws IOException
*/
public boolean makeSymLink(final String target, final String source) throws IOException {
int retVal = 0;
Process process = null;
final String[] commands = { "ln", "-s", target, source };
process = Runtime.getRuntime().exec(commands);
try {
retVal = process.waitFor();
} catch (final InterruptedException e) {
SdrUiPlugin.getDefault().logError("Unable to create symlink", e);
}
return (retVal == 0);
}
}