/******************************************************************************* * Copyright (c) 2005 RadRails.org and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Common Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html *******************************************************************************/ package org.radrails.rails.internal.ui.tail; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Device; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.console.MessageConsole; import org.eclipse.ui.console.MessageConsoleStream; import org.radrails.rails.ui.RailsUILog; import org.rubypeople.rdt.ui.text.ansi.ANSIParser; import org.rubypeople.rdt.ui.text.ansi.ANSIToken; /** * Acts similar to the UNIX 'tail' command which allows watching a file * update in real time. * * @author mbaumbach * * @version 0.5.0 */ public class Tail { /** * The file to tail. */ private File file; /** * Keeps track of the last time the file was modified. */ private long fileLength; /** * The BufferedReader used to read the file. */ private BufferedReader in; /** * The MessageConsole this tail is part of. */ private MessageConsole console; /** * The current Device. */ private Device dev = Display.getCurrent(); /** * Specifies if the thread should continue running or not. */ private boolean continueRunning; private ANSIParser parser; /** * The console output streams. One for each possible combination of font style and color */ private Map<String, MessageConsoleStream> outputStreams; /** * The number of characters to display on opening a file. */ private static final int NUM_CHARS_START = 400; /** * Creates a new Tail object for the specified filename. * * @param filename The File to tail. */ public Tail(File filename) { this.file = filename; fileLength = 0; continueRunning = false; parser = new ANSIParser(); } /** * Prints out the file's contents in real time. */ public void start(MessageConsole console) { // Setup the reader for the file and start the file length thread try { in = new BufferedReader(new FileReader(file)); } catch (FileNotFoundException e) { RailsUILog.logError("Error opening reader on tailed file", e); } this.console = console; continueRunning = true; skiptoLastNChars(NUM_CHARS_START); startFileLengthThread(); } private void skiptoLastNChars(int n) { if (file.length() > n) { try { in.skip(file.length() - n); } catch (IOException e) { RailsUILog.logError("Error skipping to end of tailed file", e); } } } /** * Reads the latest information from a file and outputs it to the screen. */ private void read() { // As long as we have a reader for the file, read in the latest if (in == null) return; // looking up the right stream should be faster than creating a new one for each token if (outputStreams == null) outputStreams = new HashMap<String, MessageConsoleStream>(); try { List<ANSIToken> tokens; while( (tokens = parser.parse(in.readLine())) != null ) { MessageConsoleStream out = null; for (ANSIToken token : tokens) { out = getStream(token); if (out != null) out.print(token.toString()); } if (out != null) out.println(); } } catch (IOException e) { RailsUILog.logError("Error reading from tailed file", e); } } private MessageConsoleStream getStream(final ANSIToken token) { if (outputStreams.containsKey(token.getAnsi())) { return outputStreams.get(token.getAnsi()); } final MessageConsoleStream out = new MessageConsoleStream(console); if (token.hasForegroundColor()) { Display.getDefault().syncExec(new Runnable() { public void run() { out.setColor(new Color(dev, token.getForegroundRGB())); } }); } if (token.hasFontStyle()) { Display.getDefault().syncExec(new Runnable() { public void run() { out.setFontStyle(token.getFontStyle()); } }); } outputStreams.put(token.getAnsi(), out); return out; } /** * Starts a thread that checks the length of a file. When the file size changes, * it calls read() so that the new changes are printed. */ private void startFileLengthThread() { Thread t = new Thread(new Runnable() { public void run() { // Run forever while( continueRunning ) { // If the file length has changed, update the file length and print the new info if ( file.length() > fileLength ) { fileLength = file.length(); read(); } Thread.yield(); } } }); t.start(); } /** * Stops the tail program. */ public void stop() { continueRunning = false; // close the file input stream try { if (in != null) in.close(); } catch (IOException e) { // ignore } // close the console output streams if (outputStreams == null) return; for (MessageConsoleStream out : outputStreams.values()) { try { if (out != null) out.close(); } catch (IOException e) { // ignore } } outputStreams = null; } public boolean isStopped() { return !continueRunning; } } // Tail