| /*=============================================================================# |
| # Copyright (c) 2009, 2020 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.jcommons.status.eplatform.EStatusUtils; |
| |
| import org.eclipse.statet.ecommons.io.FileUtil; |
| 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(), |
| EStatusUtils.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; |
| } |
| |
| } |