diff --git a/src/main/java/org/parabot/environment/handlers/exceptions/ExceptionHandler.java b/src/main/java/org/parabot/environment/handlers/exceptions/ExceptionHandler.java new file mode 100644 index 0000000..198a55a --- /dev/null +++ b/src/main/java/org/parabot/environment/handlers/exceptions/ExceptionHandler.java @@ -0,0 +1,88 @@ +package org.parabot.environment.handlers.exceptions; + +/** + * Class to be implemented that allows multiple types of exception handlers + */ +public abstract class ExceptionHandler implements Thread.UncaughtExceptionHandler { + /** + * The name of the exception handler + */ + private final String name; + + /** + * The status of the exception handler; Defines if the exception handler is enabled or disabled + */ + private boolean enabled = true; + + /** + * The type the handler is meant for + */ + private ExceptionType exceptionType; + + public ExceptionHandler(String name, ExceptionType exceptionType) { + this.name = name; + this.exceptionType = exceptionType; + } + + @Override + public void uncaughtException(Thread t, Throwable e) { + this.handle(e); + } + + /** + * Writes the exception to class extending this abstract class + * + * @param e + */ + public abstract void handle(Throwable e); + + /** + * Returns if the exception handler is enabled or disabled + * + * @return + */ + public boolean isEnabled() { + return enabled; + } + + /** + * Sets the enabled status of the exception handler + * + * @param enabled + * + * @return + */ + public ExceptionHandler setEnabled(boolean enabled) { + this.enabled = enabled; + return this; + } + + public ExceptionType getExceptionType() { + return exceptionType; + } + + public ExceptionHandler setExceptionType(ExceptionType exceptionType) { + this.exceptionType = exceptionType; + return this; + } + + public String getName() { + return name; + } + + public enum ExceptionType { + SERVER("Server"), + SCRIPT("Script"), + CLIENT("Client"); + + private String name; + + ExceptionType(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } +} diff --git a/src/main/java/org/parabot/environment/handlers/exceptions/FileExceptionHandler.java b/src/main/java/org/parabot/environment/handlers/exceptions/FileExceptionHandler.java new file mode 100644 index 0000000..f32f105 --- /dev/null +++ b/src/main/java/org/parabot/environment/handlers/exceptions/FileExceptionHandler.java @@ -0,0 +1,125 @@ +package org.parabot.environment.handlers.exceptions; + +import org.parabot.core.Directories; +import org.parabot.core.ui.utils.UILog; +import org.parabot.environment.api.utils.FileUtil; + +import javax.swing.*; +import java.awt.*; +import java.io.File; +import java.io.IOException; + +/** + * Writes exceptions to a file and reports the file location back to the user + */ +public class FileExceptionHandler extends ExceptionHandler { + /** + * Directory where the reports get written to + */ + private final File reportsDirectory; + + /** + * Defines if the alert should popup during this client instance again + */ + private boolean ignored = false; + + /** + * Initializes the exception handler and ensures the reports directory is created and writable + */ + public FileExceptionHandler(ExceptionType exceptionType) { + super("File exception handler", exceptionType); + + this.reportsDirectory = new File(Directories.getWorkspace(), "reports"); + if (!this.reportsDirectory.exists() || !this.reportsDirectory.isDirectory()) { + this.reportsDirectory.mkdir(); + } + + this.cleanOldErrors(); + } + + @Override + public void handle(Throwable e) { + File report = new File(this.reportsDirectory, "report-" + (System.currentTimeMillis() / 1000) + ".txt"); + try { + report.createNewFile(); + + StringBuilder reportContent = new StringBuilder(); + reportContent.append(e.getMessage() + "\n\n"); + + for (StackTraceElement stackTraceElement : e.getStackTrace()) { + reportContent.append(stackTraceElement); + } + + FileUtil.writeFileContents(report, reportContent.toString()); + + if (!ignored) { + displayAlert(report); + } + } catch (IOException ex) { + ex.printStackTrace(); + } + } + + public boolean isIgnored() { + return ignored; + } + + public FileExceptionHandler setIgnored(boolean ignored) { + this.ignored = ignored; + return this; + } + + public File getReportsDirectory() { + return reportsDirectory; + } + + /** + * Displays the dialog of the alert + * + * @param report + */ + private void displayAlert(File report) { + int response = UILog.alert( + "Error occurred", + "We are sorry to inform you that an error occurred within Parabot.\n\n" + + "The error has been written to a report file.\n" + + "Please report the error to the Parabot staff with as much information as possible.", + new Object[]{ + "Close", + "Open report", + "Ignore " + this.getExceptionType().getName().toLowerCase() + " errors" }, + 1, + JOptionPane.WARNING_MESSAGE + ); + + switch (response) { + case 1: + try { + Desktop.getDesktop().open(report); + } catch (Exception ex) { + ex.printStackTrace(); + } + break; + case 2: + ignored = true; + break; + + } + } + + /** + * Remove errors older than 24 hours + */ + private void cleanOldErrors() { + File[] reports = this.reportsDirectory.listFiles(); + if (reports != null) { + for (File report : reports) { + if (report.isFile()) { + if ((System.currentTimeMillis() - report.lastModified()) / 1000 > 60 * 60 * 24) { + report.delete(); + } + } + } + } + } +} diff --git a/src/main/java/org/parabot/environment/servers/executers/ServerExecuter.java b/src/main/java/org/parabot/environment/servers/executers/ServerExecuter.java index cae7c5f..1fd6df9 100644 --- a/src/main/java/org/parabot/environment/servers/executers/ServerExecuter.java +++ b/src/main/java/org/parabot/environment/servers/executers/ServerExecuter.java @@ -3,6 +3,8 @@ package org.parabot.environment.servers.executers; import org.parabot.core.Context; import org.parabot.core.ui.BotUI; import org.parabot.core.ui.components.PaintComponent; +import org.parabot.environment.handlers.exceptions.ExceptionHandler; +import org.parabot.environment.handlers.exceptions.FileExceptionHandler; import org.parabot.environment.servers.ServerProvider; /** @@ -15,7 +17,7 @@ public abstract class ServerExecuter { public abstract void run(); public void finalize(final ServerProvider provider, final String serverName) { - new Thread(new Runnable() { + Thread serverThread = new Thread(new Runnable() { @Override public void run() { try { @@ -30,7 +32,11 @@ public abstract class ServerExecuter { t.printStackTrace(); } } - }).start(); + }); + + serverThread.setUncaughtExceptionHandler(new FileExceptionHandler(ExceptionHandler.ExceptionType.SERVER)); + + serverThread.start(); } }