blob: b0e1b0712ffcbbca07c03029e887cd93f277b4ab [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2009, 2019 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.nico.ui.util;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.util.EnumSet;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.core.databinding.observable.Realm;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.observable.value.WritableValue;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.jobs.IJobManager;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.databinding.swt.typed.WidgetProperties;
import org.eclipse.jface.databinding.wizard.WizardPageSupport;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.text.AbstractDocument;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Group;
import org.eclipse.ui.console.TextConsoleViewer;
import org.eclipse.ui.statushandlers.StatusManager;
import org.eclipse.statet.ecommons.databinding.jface.DataBindingSupport;
import org.eclipse.statet.ecommons.io.FileUtil;
import org.eclipse.statet.ecommons.ui.dialogs.DialogUtils;
import org.eclipse.statet.ecommons.ui.util.LayoutUtils;
import org.eclipse.statet.ecommons.ui.util.UIAccess;
import org.eclipse.statet.ecommons.ui.util.VariableFilterUtils;
import org.eclipse.statet.internal.nico.ui.NicoUIPlugin;
import org.eclipse.statet.nico.core.runtime.SubmitType;
import org.eclipse.statet.nico.core.runtime.ToolProcess;
import org.eclipse.statet.nico.core.util.TrackWriter;
import org.eclipse.statet.nico.core.util.TrackingConfiguration;
import org.eclipse.statet.nico.ui.NicoUI;
import org.eclipse.statet.nico.ui.console.NIConsoleOutputStream;
import org.eclipse.statet.nico.ui.console.NIConsolePage;
/**
* Wizard to export the console output
*/
public class ExportConsoleOutputWizard extends Wizard {
private static final String FILE_HISTORY_SETTINGSKEY= "FileLocation_history";
protected static class ConfigurationPage extends WizardPage {
private final NIConsolePage consolePage;
private final TrackingConfiguration config;
private final IObservableValue<Boolean> openValue;
private TrackingConfigurationComposite configControl;
private Button openControl;
private DataBindingSupport dataBinding;
public ConfigurationPage(final NIConsolePage page, final TrackingConfiguration config, final boolean selectionMode) {
super("ConfigureConsoleExportPage"); //$NON-NLS-1$
this.consolePage= page;
setTitle(selectionMode ? "Export Selected Output" : "Export Current Output");
setDescription("Select the content to export and the destination file.");
this.config= config;
final Realm realm= Realm.getDefault();
this.openValue= new WritableValue<>(realm, false, Boolean.class);
}
@Override
public void createControl(final Composite parent) {
initializeDialogUnits(parent);
final Composite composite= new Composite(parent, SWT.NONE);
composite.setLayout(LayoutUtils.newContentGrid(1));
this.configControl= createTrackingControl(composite);
this.configControl.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
this.configControl.getPathInput().getValidator().setOnLateResolve(IStatus.ERROR);
this.configControl.getPathInput().setShowInsertVariable(true,
VariableFilterUtils.DEFAULT_NON_ITERACTIVE_FILTERS,
this.consolePage.getTool().getWorkspaceData().getStringVariables() );
this.configControl.setInput(this.config);
final Composite additionalOptions= createAdditionalOptions(composite);
additionalOptions.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
LayoutUtils.addSmallFiller(composite, true);
final ToolInfoGroup info= new ToolInfoGroup(composite, this.consolePage.getTool());
info.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
Dialog.applyDialogFont(composite);
setControl(composite);
this.configControl.getPathInput().setHistory(getDialogSettings().getArray(FILE_HISTORY_SETTINGSKEY));
this.dataBinding= new DataBindingSupport(composite);
addBindings(this.dataBinding);
WizardPageSupport.create(this, this.dataBinding.getContext());
}
protected TrackingConfigurationComposite createTrackingControl(final Composite parent) {
return new TrackingConfigurationComposite(parent) {
@Override
protected boolean enableFullMode() {
return false;
}
@Override
protected boolean enableFilePathAsCombo() {
return true;
}
@Override
protected EnumSet<SubmitType> getEditableSubmitTypes() {
return EnumSet.of(SubmitType.OTHER);
}
};
}
protected Composite createAdditionalOptions(final Composite parent) {
final Group composite= new Group(parent, SWT.NONE);
composite.setText("Actions:");
composite.setLayout(LayoutUtils.newGroupGrid(1));
this.openControl= new Button(composite, SWT.CHECK);
this.openControl.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
this.openControl.setText("Open in &Editor");
return composite;
}
protected void addBindings(final DataBindingSupport db) {
this.configControl.addBindings(db);
db.getContext().bindValue(
WidgetProperties.buttonSelection()
.observe(this.openControl),
this.openValue );
}
public boolean getOpenInEditor() {
return this.openValue.getValue().booleanValue();
}
protected void saveSettings() {
final IDialogSettings settings= getDialogSettings();
DialogUtils.saveHistorySettings(settings, FILE_HISTORY_SETTINGSKEY, this.config.getFilePath());
}
}
private TrackingConfiguration config;
private final int selectionLength;
private final NIConsolePage consolePage;
private ConfigurationPage configPage;
public ExportConsoleOutputWizard(final NIConsolePage consolePage) {
this.consolePage= consolePage;
this.selectionLength= ((ITextSelection) consolePage.getOutputViewer().getSelection()).getLength();
setWindowTitle("Export Console Output");
setNeedsProgressMonitor(true);
setDialogSettings(DialogUtils.getDialogSettings(NicoUIPlugin.getInstance(), "tools/ExportConsoleOutputWizard"));
}
protected TrackingConfiguration createTrackingConfiguration() {
final TrackingConfiguration config= new TrackingConfiguration(""); //$NON-NLS-1$
config.getSubmitTypes().remove(SubmitType.OTHER);
return config;
}
@Override
public void addPages() {
this.config= createTrackingConfiguration();
this.configPage= new ConfigurationPage(this.consolePage, this.config, this.selectionLength > 0);
addPage(this.configPage);
}
@Override
public boolean performFinish() {
this.configPage.saveSettings();
final boolean openInEditor= this.configPage.getOpenInEditor();
try {
getContainer().run(true, true, new IRunnableWithProgress() {
@Override
public void run(final IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
final SubMonitor m= SubMonitor.convert(monitor, "Export Output", 100);
final TextConsoleViewer outputViewer= ExportConsoleOutputWizard.this.consolePage.getOutputViewer();
final AbstractDocument document= (AbstractDocument) outputViewer.getDocument();
final IJobManager jobManager= Job.getJobManager();
final ISchedulingRule schedulingRule= ExportConsoleOutputWizard.this.consolePage.getConsole().getSchedulingRule();
jobManager.beginRule(schedulingRule, m.newChild(1));
try {
if (ExportConsoleOutputWizard.this.selectionLength > 0) {
final AtomicReference<ITextSelection> currentSelection= new AtomicReference<>();
getShell().getDisplay().syncExec(new Runnable() {
@Override
public void run() {
final ITextSelection selection= (ITextSelection) outputViewer.getSelection();
if (selection.getLength() != ExportConsoleOutputWizard.this.selectionLength) {
final boolean continueExport= MessageDialog.openQuestion(getShell(), "Export Output",
"The selection is changed due to updates in the console. Do you want to continue nevertheless?");
if (!continueExport) {
return;
}
}
currentSelection.set(selection);
}
});
final ITextSelection selection= currentSelection.get();
if (selection == null) {
return;
}
export(document, selection.getOffset(), selection.getLength(), openInEditor, m);
}
else {
export(document, 0, document.getLength(), openInEditor, m);
}
}
finally {
jobManager.endRule(schedulingRule);
}
}
});
}
catch (final InvocationTargetException e) {
final Throwable cause= e.getCause();
StatusManager.getManager().handle(new Status(IStatus.ERROR, NicoUI.BUNDLE_ID, -1,
"An error occurred when exporting console output to file.", cause),
StatusManager.LOG | StatusManager.SHOW);
return !(cause instanceof CoreException || cause instanceof IOException);
}
catch (final InterruptedException e) {
}
return true;
}
private void export(final AbstractDocument document, final int offset, final int length, final boolean openInEditor,
final SubMonitor m) throws InvocationTargetException {
m.setWorkRemaining(1 + 20);
OutputStream outputStream= null;
Writer outputWriter= null;
try {
String filePath= this.config.getFilePath();
filePath= TrackWriter.resolveVariables(filePath, this.consolePage.getTool().getWorkspaceData());
final IFileStore fileStore= FileUtil.getFileStore(filePath);
outputStream= fileStore.openOutputStream(this.config.getFileMode(), m.newChild(1));
if (fileStore.fetchInfo().getLength() <= 0L) {
FileUtil.prepareTextOutput(outputStream, this.config.getFileEncoding());
}
outputWriter= new BufferedWriter(new OutputStreamWriter(outputStream, this.config.getFileEncoding()));
if (this.config.getPrependTimestamp()) {
final ToolProcess process= this.consolePage.getConsole().getProcess();
outputWriter.append(process.createTimestampComment(process.getConnectionTimestamp()));
}
if (this.config.getTrackStreamInfo() && this.config.getTrackStreamInput()
&& this.config.getTrackStreamOutput() && !this.config.getTrackStreamOutputTruncate()
&& this.config.getSubmitTypes().contains(SubmitType.OTHER) ) {
int pOffset= offset;
int pLength= length;
while (pLength > 0) {
final int currentLength= Math.min(pLength, 32768);
outputWriter.append(document.get(pOffset, currentLength));
pOffset+= currentLength;
pLength-= currentLength;
}
}
else {
final ITypedRegion[] partitions= document.getDocumentPartitioner().computePartitioning(offset, length);
final SubMonitor m1= m.newChild(20);
int workRemaining= partitions.length;
for (final ITypedRegion partition : partitions) {
m1.setWorkRemaining(workRemaining--);
final String type= partition.getType();
String text2= null;
if (type == null) {
continue;
}
boolean truncate= false;
if (type == NIConsoleOutputStream.INFO_STREAM_ID) {
if (!this.config.getTrackStreamInfo()) {
continue;
}
}
else if (type == NIConsoleOutputStream.OTHER_TASKS_INFO_STREAM_ID) {
if (!this.config.getTrackStreamInfo()
|| !this.config.getSubmitTypes().contains(SubmitType.OTHER)) {
continue;
}
}
else if (type == NIConsoleOutputStream.STD_INPUT_STREAM_ID) {
if (!this.config.getTrackStreamInput()) {
continue;
}
}
else if (type == NIConsoleOutputStream.OTHER_TASKS_STD_INPUT_STREAM_ID) {
if (!this.config.getTrackStreamInput()
|| !this.config.getSubmitTypes().contains(SubmitType.OTHER)) {
continue;
}
}
else if (type == NIConsoleOutputStream.STD_OUTPUT_STREAM_ID) {
if (!this.config.getTrackStreamOutput()) {
continue;
}
truncate= this.config.getTrackStreamOutputTruncate();
}
else if (type == NIConsoleOutputStream.OTHER_TASKS_INFO_STREAM_ID) {
if (!this.config.getTrackStreamOutput()
|| !this.config.getSubmitTypes().contains(SubmitType.OTHER)) {
continue;
}
truncate= this.config.getTrackStreamOutputTruncate();
}
else if (type == NIConsoleOutputStream.STD_ERROR_STREAM_ID) {
if (!this.config.getTrackStreamOutput()) {
continue;
}
}
else if (type == NIConsoleOutputStream.OTHER_TASKS_STD_ERROR_STREAM_ID) {
if (!this.config.getTrackStreamOutput()
|| !this.config.getSubmitTypes().contains(SubmitType.OTHER)) {
continue;
}
}
else {
}
int pOffset= Math.max(offset, partition.getOffset());
int pLength= Math.min(offset+length, partition.getOffset()+partition.getLength()) - pOffset;
if (truncate) {
final int firstLine= document.getLineOfOffset(pOffset);
final int lastLine= document.getLineOfOffset(pOffset+pLength);
if (lastLine - firstLine + 1 > this.config.getTrackStreamOutputTruncateLines()) {
pLength= document.getLineOffset(firstLine + this.config.getTrackStreamOutputTruncateLines()) - pOffset;
text2= "[...] (truncated)\n\n";
}
}
while (pLength > 0) {
final int currentLength= Math.min(pLength, 32768);
outputWriter.append(document.get(pOffset, currentLength));
pOffset+= currentLength;
pLength-= currentLength;
}
if (text2 != null) {
outputWriter.append(text2);
}
}
}
outputWriter.close();
outputWriter= null;
if (openInEditor) {
UIAccess.getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
OpenTrackingFilesContributionItem.open("export", fileStore);
}
});
}
}
catch (final Exception e) {
throw new InvocationTargetException(e);
}
finally {
if (outputWriter != null) {
try {
outputWriter.close();
} catch (final IOException ignore) {}
}
else if (outputStream != null) {
try {
outputStream.close();
} catch (final IOException ignore) {}
}
}
}
}