blob: 29e88d4e32c8b81893d0104e68c9034b89b322f3 [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.core.util;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.variables.IDynamicVariable;
import org.eclipse.debug.core.IStreamListener;
import org.eclipse.debug.core.model.IStreamMonitor;
import org.eclipse.osgi.util.NLS;
import org.eclipse.statet.jcommons.lang.Disposable;
import org.eclipse.statet.jcommons.status.ErrorStatus;
import org.eclipse.statet.jcommons.status.ProgressMonitor;
import org.eclipse.statet.jcommons.status.Status;
import org.eclipse.statet.jcommons.status.StatusException;
import org.eclipse.statet.jcommons.status.Statuses;
import org.eclipse.statet.jcommons.status.WarningStatus;
import org.eclipse.statet.ecommons.io.FileUtil;
import org.eclipse.statet.ecommons.runtime.core.util.StatusUtils;
import org.eclipse.statet.ecommons.variables.core.ILocationVariable;
import org.eclipse.statet.ecommons.variables.core.VariableText;
import org.eclipse.statet.ecommons.variables.core.WrappedDynamicVariable;
import org.eclipse.statet.internal.nico.core.NicoCorePlugin;
import org.eclipse.statet.nico.core.NicoCore;
import org.eclipse.statet.nico.core.runtime.ConsoleService;
import org.eclipse.statet.nico.core.runtime.ITrack;
import org.eclipse.statet.nico.core.runtime.SubmitType;
import org.eclipse.statet.nico.core.runtime.ToolController;
import org.eclipse.statet.nico.core.runtime.ToolProcess;
import org.eclipse.statet.nico.core.runtime.ToolStreamMonitor;
import org.eclipse.statet.nico.core.runtime.ToolStreamProxy;
import org.eclipse.statet.nico.core.runtime.ToolWorkspace;
public class TrackWriter implements ITrack, IStreamListener, Disposable {
private static final String TRUNCATE_INFO = "[...] (truncated)\n\n";
public static String getTruncateInfo() {
return TRUNCATE_INFO;
}
public static String resolveVariables(final String path, final ToolWorkspace workspace) throws CoreException {
final List<IDynamicVariable> variables = workspace.getStringVariables();
final List<IDynamicVariable> checkedVariables= new ArrayList<>(variables.size());
for (final IDynamicVariable variable : variables) {
if (variable instanceof ILocationVariable) {
checkedVariables.add(variable);
}
else {
checkedVariables.add(new WrappedDynamicVariable(variable) {
@Override
public String getValue(final String argument) throws CoreException {
return super.getValue(argument).replaceAll("\\\\|\\/|\\:", "-"); //$NON-NLS-1$ //$NON-NLS-2$
}
});
}
}
final VariableText text = new VariableText(path, checkedVariables, true);
text.performInitialStringSubstitution(true);
text.performFinalStringSubstitution(null);
return text.getText();
}
private final ToolController controller;
private final TrackingConfiguration config;
private IFileStore storeFile;
private Writer outputWriter;
private IStreamListener inputListener;
private IStreamListener outputListener;
private int trumcateMax;
private int truncateCurrent;
public TrackWriter(final ToolController controller, final TrackingConfiguration config) {
if (controller == null || config == null) {
throw new NullPointerException();
}
this.controller = controller;
this.config = config;
}
public Status init(final ProgressMonitor m) throws StatusException {
OutputStream outputStream = null;
try {
m.setWorkRemaining(2 + 1);
try {
this.storeFile = resolveTrackingPath(this.config.getFilePath());
}
catch (final CoreException e) {
throw new StatusException(new ErrorStatus(NicoCore.BUNDLE_ID,
"Failed to resolve path of the tracking file.",
e ));
}
if (this.config.getId().equals(HistoryTrackingConfiguration.HISTORY_TRACKING_ID)
&& ((HistoryTrackingConfiguration) this.config).getLoadHistory()
&& this.storeFile.fetchInfo().exists()) {
this.controller.getTool().getHistory().load(this.storeFile, this.config.getFileEncoding(),
false, m.newSubMonitor(2) );
}
outputStream = this.storeFile.openOutputStream(this.config.getFileMode(),
StatusUtils.convert(m.newSubMonitor(1)) );
if (this.storeFile.fetchInfo().getLength() <= 0L) {
FileUtil.prepareTextOutput(outputStream, this.config.getFileEncoding());
}
this.outputWriter = new BufferedWriter(new OutputStreamWriter(outputStream, this.config.getFileEncoding()));
final EnumSet<SubmitType> submitTypes = this.config.getSubmitTypes();
final ToolStreamProxy streams = this.controller.getStreams();
if (this.config.getTrackStreamInfo()) {
streams.getInfoStreamMonitor().addListener(this, submitTypes);
}
if (this.config.getTrackStreamInput()) {
this.inputListener = (this.config.getTrackStreamInputHistoryOnly()) ?
new IStreamListener() {
@Override
public void streamAppended(final String text, final IStreamMonitor monitor) {
if ((((ToolStreamMonitor) monitor).getMeta() & ConsoleService.META_HISTORY_DONTADD) == 0) {
TrackWriter.this.streamAppendedNL(text);
}
}
} :
new IStreamListener() {
@Override
public void streamAppended(final String text, final IStreamMonitor monitor) {
TrackWriter.this.streamAppended(text, monitor);
}
};
streams.getInputStreamMonitor().addListener(this.inputListener, submitTypes);
}
if (this.config.getTrackStreamOutput()) {
if (this.config.getTrackStreamOutputTruncate()) {
this.trumcateMax = this.config.getTrackStreamOutputTruncateLines();
this.outputListener = new IStreamListener() {
@Override
public void streamAppended(final String text, final IStreamMonitor monitor) {
TrackWriter.this.streamAppendedTruncateOutput(text);
}
};
}
else {
this.outputListener = this;
}
streams.getOutputStreamMonitor().addListener(this.outputListener, submitTypes);
streams.getErrorStreamMonitor().addListener(this, submitTypes);
streams.getSystemOutputMonitor().addListener(this.outputListener, submitTypes);
}
if (this.config.getPrependTimestamp()) {
final ToolProcess process = this.controller.getTool();
final String comment = process.createTimestampComment(process.getConnectionTimestamp());
try {
this.outputWriter.write(comment);
}
catch (final Exception e) {
onError();
throw e;
}
}
return Statuses.OK_STATUS;
}
catch (final Exception e) {
onError();
if (outputStream != null) {
try {
outputStream.close();
} catch (final IOException ignore) {}
}
return new WarningStatus(NicoCore.BUNDLE_ID,
NLS.bind("Could not initialize tracking ''{0}''.", this.config.getName()),
e );
}
}
protected IFileStore resolveTrackingPath(String filePath) throws CoreException {
filePath = resolveVariables(filePath, this.controller.getWorkspaceData());
return FileUtil.getFileStore(filePath);
}
@Override
public void streamAppended(final String text, final IStreamMonitor monitor) {
this.truncateCurrent = 0;
try {
this.outputWriter.write(text);
}
catch (final IOException e) {
NicoCorePlugin.logError("An error occurred when writing to the tracking file. Tracking is stopped.", e);
onError();
}
}
private void streamAppendedTruncateOutput(String text) {
if (this.truncateCurrent == Integer.MAX_VALUE) {
return;
}
String text2 = null;
if (this.truncateCurrent > this.trumcateMax) {
this.truncateCurrent = Integer.MAX_VALUE;
text = TRUNCATE_INFO;
}
else {
int next = -1;
while ((next = text.indexOf('\n', next+1)) >= 0) {
if (++this.truncateCurrent > this.trumcateMax) {
if (text.length() != next+1) {
this.truncateCurrent = Integer.MAX_VALUE;
text = text.substring(0, next+1);
text2 = TRUNCATE_INFO;
}
break;
}
}
}
try {
this.outputWriter.write(text);
if (text2 != null) {
this.outputWriter.write(text2);
}
}
catch (final IOException e) {
NicoCorePlugin.logError("An error occurred when writing to the tracking file. Tracking is stopped.", e);
onError();
}
}
private void streamAppendedNL(final String text) {
this.truncateCurrent = 0;
try {
this.outputWriter.write(text);
this.outputWriter.write('\n');
}
catch (final IOException e) {
NicoCorePlugin.logError("An error occurred when writing to the tracking file. Tracking is stopped.", e);
onError();
}
}
private void onError() {
final ToolStreamProxy streams = this.controller.getStreams();
streams.getInfoStreamMonitor().removeListener(this);
if (this.inputListener != null) {
streams.getInputStreamMonitor().removeListener(this.inputListener);
}
if (this.outputListener != null) {
streams.getOutputStreamMonitor().removeListener(this.outputListener);
streams.getErrorStreamMonitor().removeListener(this);
}
if (this.outputWriter != null) {
try {
this.outputWriter.close();
}
catch (final IOException ignore) {}
finally {
this.outputWriter = null;
}
}
dispose();
}
@Override
public void dispose() {
if (this.outputWriter != null) {
try {
this.outputWriter.close();
}
catch (final IOException e) {
NicoCorePlugin.logError("An error occurred when closing the tracking file. Tracking is stopped.", e);
}
finally {
this.outputWriter = null;
}
}
}
@Override
public String getName() {
return this.config.getName();
}
@Override
public void flush() {
final Writer writer = this.outputWriter;
if (writer != null) {
try {
writer.flush();
}
catch (final IOException e) {
}
}
}
@Override
public IFileStore getFile() {
return this.storeFile;
}
}