package org.dcarew.logviewer;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Map;
import org.eclipse.core.resources.ResourcesPlugin;
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.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.console.IOConsole;
import org.eclipse.ui.console.IOConsoleOutputStream;
import org.eclipse.ui.console.TextConsoleViewer;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.ui.progress.IWorkbenchSiteProgressService;
// TODO: see also JavaStackTraceConsoleViewer and JavaStackTraceConsole
// TODO: show next to the console view - use a perspective extension
// TODO: need to do something like listen for text changes, and scroll to the end
//
/**
*
*
* @author Devon Carew
*/
public class LogViewerView
extends ViewPart
implements IEclipseLaunchListener
{
public static final String ID = "org.dcarew.logviewer.LogViewerView";
private IOConsole console;
private TextConsoleViewer textConsoleViewer;
private IOConsoleOutputStream out;
private boolean dataWasWritten;
private FileWatcher fileWatcher;
/**
*
*/
public LogViewerView()
{
}
@Override
public void init(IViewSite site, IMemento memento)
throws PartInitException
{
super.init(site, memento);
createToolbar(site.getActionBars().getToolBarManager());
}
private void createToolbar(IToolBarManager toolBarManager)
{
toolBarManager.add(new ClearConsoleAction());
}
@Override
public void createPartControl(Composite parent)
{
console = new IOConsole("Runtime Log", LogViewerPlugin.getImageDescriptor("jsbook_obj.gif"));
console.setTabWidth(4);
textConsoleViewer = new TextConsoleViewer(parent, console);
textConsoleViewer.getTextWidget().setEditable(false);
startListening();
}
@Override
public void setFocus()
{
textConsoleViewer.getTextWidget().setFocus();
}
@Override
public void dispose()
{
stopListening();
stopWatchingFile();
super.dispose();
}
@SuppressWarnings("unchecked")
@Override
public void launchStarted(ILaunch launch, ILaunchConfiguration launchConfiguration)
{
clear();
out = console.newOutputStream();
out.setActivateOnWrite(true);
try
{
// launchConfiguration.getType().getName() == "Eclipse Application"
// location=${workspace_loc}/../runtime-EclipseApplication
Map launchAttributes = launchConfiguration.getAttributes();
String location = (String)launchAttributes.get("location");
CharSequence workspaceLocation = ResourcesPlugin.getWorkspace().getRoot().getLocation().toString();
location = location.replace(
(CharSequence)"${workspace_loc}", workspaceLocation);
watchFile(location);
}
catch (CoreException ce)
{
LogViewerPlugin.log(ce);
}
}
private void watchFile(String filePath)
{
// /.metadata/.log
stopWatchingFile();
IPath path = new Path(filePath);
path = path.append(".metadata");
path = path.append(".log");
final File file = path.toFile();
fileWatcher = new FileWatcher(file);
asyncExec(new Runnable() {
public void run()
{
setContentDescription(file.toString());
}
});
}
private void stopWatchingFile()
{
if (fileWatcher != null)
fileWatcher.dispose();
fileWatcher = null;
}
@Override
public void launchEnded(ILaunch launch, ILaunchConfiguration launchConfiguration)
{
if (dataWasWritten())
{
asyncExec(new Runnable() {
@Override
public void run()
{
IWorkbenchSiteProgressService progressService =
(IWorkbenchSiteProgressService)getSite().getAdapter(IWorkbenchSiteProgressService.class);
progressService.decrementBusy();
progressService.warnOfContentChange();
}
});
}
stopWatchingFile();
}
private void asyncExec(Runnable runnable)
{
getSite().getShell().getDisplay().asyncExec(runnable);
}
private void clear()
{
console.clearConsole();
dataWasWritten = false;
stopWatchingFile();
asyncExec(new Runnable() {
public void run()
{
setContentDescription("");
}
});
}
private void writeData(byte[] data)
{
if (data == null || data.length == 0)
return;
try
{
out.write(data);
ensureTailIsVisible(data.length);
}
catch (IOException e)
{
e.printStackTrace();
}
if (!dataWasWritten())
{
dataWasWritten = true;
asyncExec(new Runnable() {
@Override
public void run()
{
IWorkbenchSiteProgressService progressService =
(IWorkbenchSiteProgressService)getSite().getAdapter(IWorkbenchSiteProgressService.class);
progressService.incrementBusy();
}
});
}
}
private void ensureTailIsVisible(final int charsWritten)
{
asyncExec(new Runnable() {
@Override
public void run()
{
// TODO: make sure the last line is visible -
IDocument document = textConsoleViewer.getDocument();
int lastPosition = document.getLength() - 1;
if (lastPosition > 0)
{
try
{
IRegion lineInfo = document.getLineInformationOfOffset(lastPosition);
if (textConsoleViewer.getBottomIndexEndOffset() < lineInfo.getOffset())
{
if (lineInfo.getLength() > 0)
textConsoleViewer.revealRange(lineInfo.getOffset(), 1);
else
textConsoleViewer.revealRange(lineInfo.getOffset(), 0);
}
}
catch (BadLocationException ble)
{
ble.printStackTrace();
}
}
}
});
}
private boolean dataWasWritten()
{
return dataWasWritten;
}
private void startListening()
{
LogViewerPlugin.getPlugin().addEclipseLaunchListener(this);
}
private void stopListening()
{
LogViewerPlugin.getPlugin().removeEclipseLaunchListener(this);
}
private class ClearConsoleAction
extends Action
{
public ClearConsoleAction()
{
super("Clear Log Viewer", LogViewerPlugin.getImageDescriptor("search_rem.gif"));
}
@Override
public void run()
{
console.clearConsole();
if (fileWatcher == null)
{
asyncExec(new Runnable() {
public void run()
{
setContentDescription("");
}
});
}
}
}
private class FileWatcher
extends Job
{
private File file;
private long timestamp = -1;
private long lastReadPoint;
public FileWatcher(File file)
{
super("Log Viewer");
this.file = file;
setSystem(true);
schedule(1000);
}
@Override
protected IStatus run(IProgressMonitor monitor)
{
updateFile(monitor);
schedule(200);
return Status.OK_STATUS;
}
private void updateFile(IProgressMonitor monitor)
{
if (file.exists() && timestamp == -1)
{
byte[] data = readAll(file);
writeData(data);
timestamp = file.lastModified();
lastReadPoint = file.length();
}
else if (!file.exists() && timestamp != -1)
{
handleFileDeleted();
}
else if (!file.exists())
{
return;
}
else if (file.lastModified() != timestamp)
{
byte[] data = readFrom(file, lastReadPoint);
writeData(data);
timestamp = file.lastModified();
lastReadPoint = file.length();
timestamp = file.lastModified();
}
}
private byte[] readAll(File file)
{
return readFrom(file, 0);
}
private byte[] readFrom(File file, long readFrom)
{
long fileLength = file.length();
if ((fileLength - readFrom) == 0)
return new byte[0];
// TODO: fix - this should not occur
if (fileLength - readFrom < 0)
return null;
try
{
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
try
{
byte[] data = new byte[(int)(fileLength - readFrom)];
randomAccessFile.seek(readFrom);
randomAccessFile.readFully(data);
return data;
}
catch (IOException ioe)
{
return null;
}
finally
{
try
{
randomAccessFile.close();
}
catch (IOException e)
{
}
}
}
catch (FileNotFoundException e)
{
return null;
}
}
private void handleFileDeleted()
{
if (dataWasWritten())
{
console.clearConsole();
dataWasWritten = false;
timestamp = -1;
lastReadPoint = 0;
asyncExec(new Runnable() {
@Override
public void run()
{
IWorkbenchSiteProgressService progressService =
(IWorkbenchSiteProgressService)getSite().getAdapter(IWorkbenchSiteProgressService.class);
progressService.decrementBusy();
}
});
}
}
public void dispose()
{
cancel();
}
}
}