/*******************************************************************************
* Copyright (c) 2015 Ericsson
*
* 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
*
* Contributors:
* Bernd Hufmann - Initial API and implementation
*******************************************************************************/
package org.eclipse.tracecompass.internal.tmf.remote.ui.wizards.fetch.model;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
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.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.jface.operation.ModalContext;
import org.eclipse.osgi.util.NLS;
import org.eclipse.remote.core.IRemoteFileService;
import org.eclipse.tracecompass.internal.tmf.remote.ui.Activator;
import org.eclipse.tracecompass.internal.tmf.remote.ui.messages.RemoteMessages;
import org.eclipse.tracecompass.internal.tmf.ui.project.wizards.tracepkg.TracePackageElement;
import org.eclipse.tracecompass.internal.tmf.ui.project.wizards.tracepkg.TracePackageTraceElement;
import org.eclipse.tracecompass.tmf.core.project.model.TmfTraceCoreUtils;
import org.eclipse.tracecompass.tmf.core.project.model.TmfTraceType;
import org.eclipse.tracecompass.tmf.remote.core.proxy.RemoteSystemProxy;
/**
* An operation that generates the manifest based on a profile and the content
* of a remote node.
*
* @author Bernd Hufmann
*/
public class RemoteGenerateManifestOperation extends AbstractGenerateManifestOperation {
// ------------------------------------------------------------------------
// Attributes
// ------------------------------------------------------------------------
private RemoteImportProfileElement fProfile;
private Set<IPath> fDirectoryTraces = new HashSet<>();
// ------------------------------------------------------------------------
// Constructor(s)
// ------------------------------------------------------------------------
/**
* Constructs a new import operation for generating the manifest
*
* @param profile
* the input profile element
*/
public RemoteGenerateManifestOperation(RemoteImportProfileElement profile) {
super(profile.getText());
fProfile = profile;
}
// ------------------------------------------------------------------------
// Operation(s)
// ------------------------------------------------------------------------
@Override
public void run(IProgressMonitor monitor) {
try {
monitor.worked(1);
String root = null;
List<TracePackageElement> resultElementList = new ArrayList<>();
SubMonitor subMonitor = SubMonitor.convert(monitor, fProfile.getChildren().length * 2);
List<RemoteImportConnectionNodeElement> connectionNodes = fProfile.getConnectionNodeElements();
for (RemoteImportConnectionNodeElement connectionNode : connectionNodes) {
RemoteSystemProxy proxy = connectionNode.getRemoteSystemProxy();
// create new element to decouple from input element
RemoteImportConnectionNodeElement outputConnectionNode =
new RemoteImportConnectionNodeElement(null, connectionNode.getName(), connectionNode.getURI());
resultElementList.add(outputConnectionNode);
for (TracePackageElement element : connectionNode.getChildren()) {
if (element instanceof RemoteImportTraceGroupElement) {
ModalContext.checkCanceled(monitor);
RemoteImportTraceGroupElement traceGroup = (RemoteImportTraceGroupElement) element;
root = traceGroup.getRootImportPath();
TracePackageElement[] traceElements = traceGroup.getChildren();
fTemplatePatternsToTraceElements = generatePatterns(traceElements);
IRemoteFileService fs = proxy.getRemoteConnection().getService(IRemoteFileService.class);
if (fs == null) {
continue;
}
final IFileStore remoteFolder = fs.getResource(root);
// make sure that remote directory is read and not cached
int recursionLevel = 0;
// create new element to decouple from input element
RemoteImportTraceGroupElement outputTraceGroup =
new RemoteImportTraceGroupElement(outputConnectionNode, traceGroup.getRootImportPath());
outputTraceGroup.setRecursive(traceGroup.isRecursive());
generateElementsFromArchive(outputTraceGroup, outputTraceGroup, remoteFolder, recursionLevel, subMonitor.newChild(1));
filterElements(outputTraceGroup);
}
}
}
setResultElements(resultElementList.toArray(new TracePackageElement[0]));
setStatus(Status.OK_STATUS);
} catch (InterruptedException e) {
setStatus(Status.CANCEL_STATUS);
} catch (Exception e) {
setStatus(new Status(IStatus.ERROR, Activator.PLUGIN_ID, NLS.bind(RemoteMessages.RemoteGenerateManifest_GenerateProfileManifestError, fProfile.getText()), e));
}
}
// ------------------------------------------------------------------------
// Helper methods
// ------------------------------------------------------------------------
/**
* Scan traceFolder for files that match the patterns specified in the
* template file. When there is a match, the trace package element is used
* to determine the trace name and trace type.
*
* @param traceGroup
* The parent trace group element
* @param parentElement
* The immediate parent trace group or folder element
* @param traceFolder
* The folder to scan
* @param recursionLevel
* The recursion level (needed to find directory traces under the traceFolder
* @param monitor
* The progress monitor
* @throws CoreException
* Thrown by the file system implementation
* @throws InterruptedException
* Thrown if operation was cancelled
*/
private void generateElementsFromArchive(
final RemoteImportTraceGroupElement traceGroup,
final TracePackageElement parentElement,
final IFileStore traceFolder,
final int recursionLevel,
IProgressMonitor monitor)
throws CoreException, InterruptedException {
int localRecursionLevel = recursionLevel + 1;
IFileStore[] sources = traceFolder.childStores(EFS.NONE, monitor);
for (int i = 0; i < sources.length; i++) {
ModalContext.checkCanceled(monitor);
SubMonitor subMonitor = SubMonitor.convert(monitor, sources.length);
IFileStore fileStore = sources[i];
IPath fullArchivePath = TmfTraceCoreUtils.newSafePath(fileStore.toURI().getPath());
IFileInfo sourceInfo = fileStore.fetchInfo();
if (!sourceInfo.isDirectory()) {
String rootPathString = traceGroup.getRootImportPath();
IPath rootPath = TmfTraceCoreUtils.newSafePath(rootPathString);
IPath relativeTracePath = Path.EMPTY;
if (rootPath.isPrefixOf(fullArchivePath)) {
relativeTracePath = fullArchivePath.makeRelativeTo(rootPath);
}
Entry<Pattern, TracePackageTraceElement> matchingTemplateEntry = getMatchingTemplateElement(relativeTracePath);
if (matchingTemplateEntry != null) {
TracePackageTraceElement matchingTemplateElement = matchingTemplateEntry.getValue();
String traceType = matchingTemplateElement.getTraceType();
// If a single file is part of a directory trace, use the parent directory instead
TracePackageElement parent = parentElement;
if (matchesDirectoryTrace(relativeTracePath, matchingTemplateEntry)) {
fullArchivePath = fullArchivePath.removeLastSegments(1);
fDirectoryTraces.add(fullArchivePath);
fileStore = fileStore.getParent();
sourceInfo = fileStore.fetchInfo();
parent = parentElement.getParent();
// Let the auto-detection choose the best trace type
traceType = null;
} else if ((localRecursionLevel > 1) && (!traceGroup.isRecursive())) {
// Don't consider file traces on level 2 if it's not recursive
continue;
}
if (sourceInfo.getLength() > 0 || sourceInfo.isDirectory()) {
// Only add non-empty files
String traceName = fullArchivePath.lastSegment();
String fileName = fileStore.getName();
// create new elements to decouple from input elements
TracePackageTraceElement traceElement = new TracePackageTraceElement(parent, traceName, traceType);
RemoteImportTraceFilesElement tracePackageFilesElement = new RemoteImportTraceFilesElement(traceElement, fileName, fileStore);
tracePackageFilesElement.setVisible(false);
}
}
} else {
if (traceGroup.isRecursive() || localRecursionLevel < 2) {
RemoteImportFolderElement folder = new RemoteImportFolderElement(parentElement, fileStore.getName());
generateElementsFromArchive(traceGroup, folder, fileStore, localRecursionLevel, subMonitor);
}
}
}
}
/*
* Filter elements to avoid having files of directory traces listed as
* separate traces.
*/
private void filterElements(TracePackageElement parentElement) {
for (TracePackageElement childElement : parentElement.getChildren()) {
filterElements(childElement);
if (childElement instanceof TracePackageTraceElement) {
// no need to do length check here
RemoteImportTraceFilesElement filesElement = (RemoteImportTraceFilesElement) childElement.getChildren()[0];
IFileStore parentFile = filesElement.getRemoteFile().getParent();
if (fDirectoryTraces.contains(TmfTraceCoreUtils.newSafePath(parentFile.toURI().getPath()))) {
removeChild(childElement, parentElement);
continue;
}
IFileStore grandParentFile = parentFile.getParent();
if (grandParentFile != null && fDirectoryTraces.contains(TmfTraceCoreUtils.newSafePath(grandParentFile.toURI().getPath()))) {
// ignore file if grandparent is a directory trace
// for example: file is a index file of a LTTng kernel trace
parentElement.removeChild(childElement);
if (parentElement.getChildren().length == 0) {
TracePackageElement grandParentElement = parentElement.getParent();
removeChild(parentElement, grandParentElement);
}
continue;
}
} else if (childElement instanceof RemoteImportFolderElement) {
if (childElement.getChildren().length == 0) {
parentElement.removeChild(childElement);
}
}
}
}
private static void removeChild(TracePackageElement childElement, TracePackageElement parentElement) {
parentElement.removeChild(childElement);
if (parentElement.getChildren().length == 0) {
if (parentElement.getParent() != null) {
parentElement.getParent().removeChild(parentElement);
}
}
}
/**
* This method takes the auto-detection case into consideration.
*
* {@inheritDoc}
*/
@Override
protected Entry<Pattern, TracePackageTraceElement> getMatchingTemplateElement(IPath fullArchivePath) {
for (Entry<Pattern, TracePackageTraceElement> entry : fTemplatePatternsToTraceElements.entrySet()) {
// Check for CTF trace (metadata)
String traceType = entry.getValue().getTraceType();
// empty string is for auto-detection
if ((traceType.isEmpty() || TmfTraceType.isDirectoryTraceType(traceType)) &&
(matchesDirectoryTrace(fullArchivePath, entry))) {
return entry;
} else if (entry.getKey().matcher(TmfTraceCoreUtils.safePathToString(fullArchivePath.toString())).matches()) {
return entry;
}
}
return null;
}
/**
* This method takes the auto-detection case into consideration.
*
* {@inheritDoc}
*/
@Override
protected boolean matchesDirectoryTrace(IPath archivePath, Entry<Pattern, TracePackageTraceElement> entry) {
if (METADATA_FILE_NAME.equals(archivePath.lastSegment())) {
IPath archiveParentPath = archivePath.removeLastSegments(1);
String parentPathString = TmfTraceCoreUtils.safePathToString(archiveParentPath.toString());
if (entry.getKey().matcher(parentPathString).matches()) {
String traceType = entry.getValue().getTraceType();
// empty string is for auto-detection
if (traceType.isEmpty() || TmfTraceType.isDirectoryTraceType(traceType)) {
return true;
}
}
}
return false;
}
}