| /******************************************************************************* |
| * Copyright (c) 2016, 2018 EfficiOS Inc., Alexandre Montplaisir and others |
| * |
| * All rights reserved. This program and the accompanying materials are |
| * made available under the terms of the Eclipse Public License 2.0 which |
| * accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| *******************************************************************************/ |
| |
| package org.eclipse.tracecompass.internal.tmf.ui.project.handlers; |
| |
| import static org.eclipse.tracecompass.common.core.NonNullUtils.checkNotNull; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.lang.reflect.InvocationTargetException; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| |
| import org.eclipse.core.commands.AbstractHandler; |
| import org.eclipse.core.commands.ExecutionEvent; |
| import org.eclipse.core.commands.ExecutionException; |
| import org.eclipse.core.resources.IContainer; |
| import org.eclipse.core.resources.IFolder; |
| 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.IStatus; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.SubMonitor; |
| import org.eclipse.jdt.annotation.NonNull; |
| import org.eclipse.jdt.annotation.NonNullByDefault; |
| import org.eclipse.jdt.annotation.Nullable; |
| import org.eclipse.jface.dialogs.MessageDialog; |
| import org.eclipse.jface.viewers.ISelection; |
| import org.eclipse.jface.viewers.IStructuredSelection; |
| import org.eclipse.jface.viewers.TreeSelection; |
| import org.eclipse.jface.window.Window; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Shell; |
| import org.eclipse.tracecompass.internal.tmf.ui.Activator; |
| import org.eclipse.tracecompass.internal.tmf.ui.project.operations.NewExperimentOperation; |
| import org.eclipse.tracecompass.internal.tmf.ui.project.operations.TmfWorkspaceModifyOperation; |
| import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem; |
| import org.eclipse.tracecompass.statesystem.core.snapshot.StateSnapshot; |
| import org.eclipse.tracecompass.tmf.core.analysis.IAnalysisModule; |
| import org.eclipse.tracecompass.tmf.core.component.ITmfEventProvider; |
| import org.eclipse.tracecompass.tmf.core.project.model.TmfTraceType; |
| import org.eclipse.tracecompass.tmf.core.project.model.TraceTypeHelper; |
| import org.eclipse.tracecompass.tmf.core.statesystem.ITmfAnalysisModuleWithStateSystems; |
| import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange; |
| import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace; |
| import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager; |
| import org.eclipse.tracecompass.tmf.core.trace.trim.ITmfTrimmableTrace; |
| import org.eclipse.tracecompass.tmf.ui.project.model.ITmfProjectModelElement; |
| import org.eclipse.tracecompass.tmf.ui.project.model.TmfCommonProjectElement; |
| import org.eclipse.tracecompass.tmf.ui.project.model.TmfExperimentElement; |
| import org.eclipse.tracecompass.tmf.ui.project.model.TmfExperimentFolder; |
| import org.eclipse.tracecompass.tmf.ui.project.model.TmfOpenTraceHelper; |
| import org.eclipse.tracecompass.tmf.ui.project.model.TmfProjectElement; |
| import org.eclipse.tracecompass.tmf.ui.project.model.TmfProjectRegistry; |
| import org.eclipse.tracecompass.tmf.ui.project.model.TmfTraceElement; |
| import org.eclipse.tracecompass.tmf.ui.project.model.TmfTraceFolder; |
| import org.eclipse.tracecompass.tmf.ui.project.model.TmfTraceTypeUIUtils; |
| import org.eclipse.tracecompass.tmf.ui.project.model.TraceUtils; |
| import org.eclipse.tracecompass.tmf.ui.project.wizards.TrimTraceDialog; |
| import org.eclipse.ui.IWorkbench; |
| import org.eclipse.ui.PlatformUI; |
| import org.eclipse.ui.handlers.HandlerUtil; |
| import org.eclipse.ui.handlers.IHandlerService; |
| |
| import com.google.common.collect.Lists; |
| |
| /** |
| * Handler for the Trace Trim operation. |
| * |
| * @author Alexandre Montplaisir |
| */ |
| @NonNullByDefault |
| public class TrimTraceHandler extends AbstractHandler { |
| |
| private static final class TraceToTrim { |
| private ITmfTrace fTrace; |
| private @Nullable ITmfTrimmableTrace fTrimmable; |
| private Path fDestinationPath; |
| private Collection<TraceToTrim> fChildren = new ArrayList<>(); |
| private TmfCommonProjectElement fElement; |
| private @Nullable IFolder fFolder = null; |
| private @Nullable ITmfProjectModelElement fDestElement = null; |
| private TmfCommonProjectElement fSourceElement; |
| |
| public static @Nullable TraceToTrim create(TmfCommonProjectElement element, Path destination) { |
| ITmfTrace trace = element.getTrace(); |
| if (trace != null) { |
| return new TraceToTrim(trace, element, element, destination); |
| } |
| if (element instanceof TmfTraceElement) { |
| TmfTraceElement traceElement = (TmfTraceElement) element; |
| trace = traceElement.getTrace(); |
| |
| if (trace != null) { |
| return new TraceToTrim(trace, element, element, destination); |
| } |
| } |
| return null; |
| } |
| |
| private static final @Nullable TraceToTrim create(TraceToTrim parent, TmfTraceElement traceElement) { |
| TmfTraceElement elementUnderTraceFolder = traceElement.getElementUnderTraceFolder(); |
| ITmfTrace trace = traceElement.getTrace(); |
| if (trace != null) { |
| List<String> elems = new ArrayList<>(); |
| ITmfProjectModelElement tempTraceElement = elementUnderTraceFolder; |
| while (tempTraceElement != null) { |
| elems.add(Objects.requireNonNull(tempTraceElement.getName())); |
| tempTraceElement = tempTraceElement.getParent(); |
| } |
| TraceTypeHelper traceType = TmfTraceType.getTraceType(elementUnderTraceFolder.getTraceType()); |
| String end = elems.remove(0); |
| elems = Lists.reverse(elems); |
| TmfTraceFolder tracesFolder = elementUnderTraceFolder.getProject().getTracesFolder(); |
| if (tracesFolder == null) { |
| return null; |
| } |
| IFolder folder = tracesFolder.getResource(); |
| // remove project name |
| elems.remove(0); |
| // remove "Traces" |
| elems.set(0, String.valueOf(parent.fDestinationPath.getFileName())); |
| for (String elem : elems) { |
| folder = folder.getFolder(elem); |
| if (!folder.exists()) { |
| try { |
| folder.create(true, true, new NullProgressMonitor()); |
| } catch (CoreException e) { |
| return null; |
| } |
| } |
| } |
| folder = folder.getFolder(end); |
| if (!traceType.isDirectoryTraceType() && !folder.exists()) { |
| try { |
| folder.create(true, true, new NullProgressMonitor()); |
| folder.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor()); |
| } catch (CoreException e) { |
| return null; |
| } |
| } |
| TmfTraceElement te = new TmfTraceElement(elementUnderTraceFolder.getName(), folder, tracesFolder) { |
| @Override |
| public String getTraceType() { |
| return String.valueOf(traceElement.getTraceType()); |
| } |
| |
| @Override |
| public void refreshTraceType() { |
| // Do nothing, we have the tracetype above |
| } |
| |
| @Override |
| public IResource[] getSupplementaryResources() { |
| return traceElement.getSupplementaryResources(); |
| } |
| }; |
| return new TraceToTrim(trace, elementUnderTraceFolder, te, (new File(te.getName())).toPath()); |
| } |
| return null; |
| } |
| |
| public TraceToTrim(ITmfTrace trace, TmfCommonProjectElement sourceElement, TmfCommonProjectElement element, Path destination) { |
| fTrace = trace; |
| fSourceElement = sourceElement; |
| fElement = element; |
| if (trace instanceof ITmfTrimmableTrace) { |
| fTrimmable = (ITmfTrimmableTrace) trace; |
| } else { |
| fTrimmable = null; |
| } |
| fFolder = (IFolder) element.getResource(); |
| // Note: getParent.getLocation is almost never a link. The |
| // folder.getLocation may be a link. |
| fDestinationPath = fFolder != null ? fFolder.getParent().getLocation().append(destination.toString()).toFile().toPath() : destination; |
| // go through children |
| for (TmfTraceElement childElement : element.getTraces()) { |
| TraceToTrim create = TraceToTrim.create(this, childElement); |
| if (create != null) { |
| fChildren.add(create); |
| } |
| } |
| } |
| |
| public IStatus trim(TmfTimeRange tr, IProgressMonitor mon) { |
| if (mon.isCanceled()) { |
| return Status.CANCEL_STATUS; |
| } |
| SubMonitor monitor = SubMonitor.convert(mon, 3); |
| List<@NonNull ITmfAnalysisModuleWithStateSystems> statesystemModules = new ArrayList<>(); |
| for (IAnalysisModule module : fTrace.getAnalysisModules()) { |
| if (module instanceof ITmfAnalysisModuleWithStateSystems) { |
| statesystemModules.add((ITmfAnalysisModuleWithStateSystems) module); |
| } |
| } |
| monitor.worked(1); |
| long snapshotTime = tr.getStartTime().toNanos(); |
| /* |
| * Perform the trace-specific trim operation. This should create the |
| * trace file(s) in the destination path. |
| */ |
| Path returnPath = fDestinationPath; |
| if (fTrimmable != null) { |
| try { |
| returnPath = fTrimmable.trim(tr, returnPath, monitor); |
| } catch (CoreException e) { |
| return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "An error occurred writing the state systems snapshots", e); //$NON-NLS-1$ |
| } |
| } |
| if (returnPath == null) { |
| return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Failed to trim trace. " + fTrimmable); //$NON-NLS-1$ |
| } |
| if (monitor.isCanceled()) { |
| return Status.CANCEL_STATUS; |
| } |
| monitor.worked(1); |
| SubMonitor ssMon = SubMonitor.convert(mon, statesystemModules.size()); |
| /* Write the snapshot files in the new trace's location. */ |
| try { |
| for (ITmfAnalysisModuleWithStateSystems module : statesystemModules) { |
| ssMon.split(1); |
| Map<String, Integer> versions = module.getProviderVersions(); |
| Iterable<ITmfStateSystem> sss = module.getStateSystems(); |
| for (ITmfStateSystem ss : sss) { |
| Integer version = versions.get(ss.getSSID()); |
| long currentEndTime = ss.getCurrentEndTime(); |
| if (snapshotTime <= currentEndTime && version != null) { |
| StateSnapshot snapshot = new StateSnapshot(ss, Math.max(snapshotTime, ss.getStartTime()), Math.min(currentEndTime, tr.getEndTime().toNanos()), version); |
| snapshot.write(returnPath); |
| } |
| } |
| } |
| |
| } catch (IOException e) { |
| return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "An error occurred writing the state systems snapshots", e); //$NON-NLS-1$ |
| } |
| for (TraceToTrim child : fChildren) { |
| IStatus status = child.trim(tr, monitor); |
| if (!status.isOK()) { |
| return status; |
| } |
| } |
| return Status.OK_STATUS; |
| } |
| |
| public IStatus importTrace(IProgressMonitor monitor) { |
| try { |
| IFolder folder = fFolder; |
| if (folder == null) { |
| return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Folder is null"); //$NON-NLS-1$ |
| } |
| ITmfProjectModelElement destElement; |
| for (TraceToTrim child : fChildren) { |
| IStatus status = child.importTrace(monitor); |
| if (!status.isOK()) { |
| return status; |
| } |
| } |
| |
| TraceTypeHelper traceTypeHelper = TmfTraceType.getTraceType(fElement.getTraceType()); |
| if (fElement instanceof TmfExperimentElement) { |
| TmfExperimentElement experimentElement = (TmfExperimentElement) fElement; |
| // create an experiment |
| TmfExperimentFolder experimentsFolder = experimentElement.getProject().getExperimentsFolder(); |
| if (experimentsFolder == null) { |
| return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Error getting experiment folder"); //$NON-NLS-1$ |
| } |
| List<TmfTraceElement> children = new ArrayList<>(); |
| for (TraceToTrim child : fChildren) { |
| ITmfProjectModelElement childDest = child.fDestElement; |
| if (childDest instanceof TmfTraceElement) { |
| children.add((TmfTraceElement) childDest); |
| } |
| } |
| |
| String name = fDestinationPath.getFileName().toString(); |
| if (name == null) { |
| return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "could not create element"); //$NON-NLS-1$ |
| } |
| NewExperimentOperation newExperimentOperation = new NewExperimentOperation(experimentsFolder, name, null, children); |
| newExperimentOperation.run(monitor); |
| experimentsFolder.refresh(); |
| destElement = experimentsFolder.getChild(name); |
| if (destElement == null) { |
| return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "could not create element"); //$NON-NLS-1$ |
| } |
| TmfTraceTypeUIUtils.setTraceType(destElement.getResource(), traceTypeHelper); |
| if (destElement instanceof TmfExperimentElement) { |
| TmfExperimentElement expElement = (TmfExperimentElement) destElement; |
| for (TraceToTrim child : fChildren) { |
| ITmfProjectModelElement childDest = child.fDestElement; |
| if (childDest instanceof TmfTraceElement && expElement.getChild(childDest.getName()) == null) { |
| expElement.addTrace((TmfTraceElement) childDest, false); |
| } |
| } |
| fSourceElement.copySupplementaryFolder(destElement.getName()); |
| destElement.refresh(); |
| fDestElement = destElement; |
| } |
| |
| } else if (fElement instanceof TmfTraceElement) { |
| // create a trace |
| String path = (traceTypeHelper.isDirectoryTraceType() ? fDestinationPath : fDestinationPath.getParent()).toAbsolutePath().toString(); |
| IPath destinationPath = org.eclipse.core.runtime.Path.fromOSString(path); |
| IResource traceResource = null; |
| String lastSegment = destinationPath.lastSegment(); |
| IContainer tempFolder = folder.getParent(); |
| tempFolder.refreshLocal(IResource.DEPTH_INFINITE, monitor); |
| // Look for a common parent. In case the folders are oddly |
| // configured. |
| while (tempFolder != null && traceResource == null) { |
| traceResource = tempFolder.findMember(lastSegment); |
| tempFolder = tempFolder.getParent(); |
| } |
| if (traceResource == null) { |
| return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Error finding common parent"); //$NON-NLS-1$ |
| } |
| IStatus ret = TmfTraceTypeUIUtils.setTraceType(traceResource, traceTypeHelper); |
| if (!ret.isOK()) { |
| return ret; |
| } |
| ITmfProjectModelElement findElement = TmfProjectRegistry.findElement(traceResource, true); |
| if (findElement instanceof TmfCommonProjectElement) { |
| fSourceElement.copySupplementaryFolder(((TmfCommonProjectElement) findElement).getElementPath()); |
| } |
| fDestElement = findElement; |
| } |
| } catch (CoreException e) { |
| return new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Error creating link", e); //$NON-NLS-1$ |
| } |
| if (monitor.isCanceled()) { |
| return Status.CANCEL_STATUS; |
| } |
| return Status.OK_STATUS; |
| } |
| |
| public void open() { |
| if (fDestElement instanceof TmfCommonProjectElement) { |
| TmfOpenTraceHelper.openTraceFromElement((TmfCommonProjectElement) fDestElement); |
| } |
| } |
| |
| public void createDir() throws IOException { |
| if (fElement instanceof TmfExperimentElement) { |
| for (TraceToTrim child : fChildren) { |
| child.createDir(); |
| } |
| } else { |
| fDestinationPath.toAbsolutePath().toFile().mkdirs(); |
| } |
| } |
| } |
| |
| /** |
| * Can this trace be trimmed? |
| * |
| * @param element |
| * the element to test |
| * @return <code>true</code> if the trace or all its children can be |
| * trimmed. |
| */ |
| private static boolean isValid(@Nullable Object element) { |
| if (element instanceof ITmfTrimmableTrace) { |
| return true; |
| } |
| if (!(element instanceof ITmfTrace)) { |
| return false; |
| } |
| ITmfTrace trace = (ITmfTrace) element; |
| if (trace.getChildren().isEmpty()) { |
| return false; |
| } |
| for (ITmfEventProvider child : trace.getChildren()) { |
| if (!isValid(child)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean isEnabled() { |
| IWorkbench workbench = PlatformUI.getWorkbench(); |
| if (workbench == null) { |
| return false; |
| } |
| @SuppressWarnings("null") |
| IHandlerService service = workbench.getService(IHandlerService.class); |
| // we need the current state, and the map, but the command and the |
| // trigger are |
| // not necessary for getCurrentSelection |
| ExecutionEvent executionEvent = new ExecutionEvent(null, Collections.emptyMap(), null, service.getCurrentState()); |
| final Object element = HandlerUtil.getCurrentSelection(executionEvent); |
| |
| if (!(element instanceof TreeSelection)) { |
| return false; |
| } |
| /* |
| * plugin.xml should have done type/count verification already |
| */ |
| Object firstElement = ((TreeSelection) element).getFirstElement(); |
| ITmfTrace trace = null; |
| if (firstElement instanceof TmfCommonProjectElement) { |
| TmfCommonProjectElement traceElem = (TmfCommonProjectElement) firstElement; |
| if (traceElem instanceof TmfTraceElement) { |
| traceElem = ((TmfTraceElement) traceElem).getElementUnderTraceFolder(); |
| } |
| trace = traceElem.getTrace(); |
| } |
| if (trace == null || !isValid(trace)) { |
| return false; |
| } |
| |
| /* Only enable the action if a time range is currently selected */ |
| TmfTraceManager tm = TmfTraceManager.getInstance(); |
| TmfTimeRange selectionRange = tm.getTraceContext(trace).getSelectionRange(); |
| return !(selectionRange.getStartTime().equals(selectionRange.getEndTime())); |
| } |
| |
| @Override |
| public @Nullable Object execute(@Nullable ExecutionEvent event) throws ExecutionException { |
| ISelection selection = HandlerUtil.getCurrentSelectionChecked(event); |
| Object firstElement = ((IStructuredSelection) selection).getFirstElement(); |
| final TmfCommonProjectElement traceElem = (firstElement instanceof TmfTraceElement) ? |
| ((TmfTraceElement) firstElement).getElementUnderTraceFolder() : (TmfCommonProjectElement) firstElement; |
| |
| ITmfTrace trace = traceElem.getTrace(); |
| |
| if (trace == null || !isValid(trace)) { |
| /* That trace is not currently opened */ |
| return null; |
| } |
| |
| /* Retrieve the current time range */ |
| final Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(); |
| TmfTraceManager tm = TmfTraceManager.getInstance(); |
| TmfTimeRange timeRange = tm.getTraceContext(trace).getSelectionRange(); |
| if (Objects.equals(timeRange.getStartTime(), timeRange.getEndTime())) { |
| MessageDialog.openError(shell, Messages.TrimTraces_InvalidTimeRange_DialogTitle, Messages.TrimTraces_InvalidTimeRange_DialogText); |
| return null; |
| } |
| |
| /* Ensure the time range is in the right direction */ |
| final TmfTimeRange tr = ((timeRange.getStartTime().compareTo(timeRange.getEndTime()) > 0) ? new TmfTimeRange(timeRange.getEndTime(), timeRange.getStartTime()) : timeRange); |
| |
| /* |
| * Pop a dialog asking the user to select a parent directory for the new |
| * trace. |
| */ |
| TrimTraceDialog dialog = new TrimTraceDialog(shell, traceElem); |
| if (dialog.open() != Window.OK) { |
| return null; |
| } |
| Object result = dialog.getFirstResult(); |
| if (result == null) { |
| /* Dialog was cancelled, take no further action. */ |
| return null; |
| } |
| |
| /* Verify that the selected path is valid and writeable */ |
| final Path destinationPath = checkNotNull(Paths.get(result.toString())); |
| |
| if (destinationPath.toFile().exists()) { |
| MessageDialog.openError(shell, Messages.TrimTraces_InvalidDirectory_DialogTitle, Messages.TrimTraces_InvalidDirectory_DialogText); |
| return null; |
| } |
| TraceToTrim toTrim = TraceToTrim.create(traceElem, destinationPath); |
| if (toTrim == null) { |
| return null; |
| } |
| try { |
| toTrim.createDir(); |
| } catch (IOException e) { |
| /* Should not happen since we have checked permissions, etc. */ |
| throw new ExecutionException(e.getMessage(), e); |
| } |
| |
| TmfWorkspaceModifyOperation trimOperation = new TmfWorkspaceModifyOperation() { |
| @Override |
| public void execute(@Nullable IProgressMonitor monitor) throws CoreException { |
| SubMonitor mon = SubMonitor.convert(monitor, 2); |
| |
| toTrim.trim(tr, mon); |
| /* |
| * Import the new trace into the current project, at the |
| * top-level. |
| */ |
| TmfProjectElement currentProjectElement = traceElem.getProject(); |
| TmfTraceFolder traceFolder = currentProjectElement.getTracesFolder(); |
| toTrim.importTrace(mon); |
| if (mon.isCanceled()) { |
| return; |
| } |
| if (traceFolder != null) { |
| Display.getDefault().asyncExec(toTrim::open); |
| } else { |
| Activator.getDefault().logWarning("Trace folder does not exist: " + toTrim.fDestinationPath); //$NON-NLS-1$ |
| } |
| } |
| }; |
| |
| try { |
| PlatformUI.getWorkbench().getProgressService().run(true, true, trimOperation); |
| } catch (InterruptedException e) { |
| Thread.currentThread().interrupt(); |
| } catch (InvocationTargetException e) { |
| TraceUtils.displayErrorMsg(Messages.TrimTraceHandler_failMsg, e.getMessage(), e); |
| } |
| |
| return null; |
| } |
| |
| } |