Merge pull request #316 from Parabot/feature/error-reporting

[FEATURE] Added exception handlers
This commit is contained in:
Jeroen Ketelaar
2019-05-22 07:21:10 -05:00
committed by GitHub
13 changed files with 344 additions and 67 deletions
+4 -4
View File
@@ -17,7 +17,7 @@
</properties> </properties>
<name>Parabot client</name> <name>Parabot client</name>
<description>The only perfect open source (Runescape private server) bot!</description> <description>The best open-source (Runescape Private Server) bot</description>
<url>http://www.parabot.org/</url> <url>http://www.parabot.org/</url>
<licenses> <licenses>
@@ -68,7 +68,7 @@
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<version>4.12</version> <version>4.12</version>
<scope>provided</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.parabot</groupId> <groupId>org.parabot</groupId>
@@ -191,13 +191,13 @@
<uniqueVersion>false</uniqueVersion> <uniqueVersion>false</uniqueVersion>
<id>parabot-maven</id> <id>parabot-maven</id>
<name>Custom Maven Repository</name> <name>Custom Maven Repository</name>
<url>ftp://maven.parabot.org</url> <url>ftp://maven.parabot.org/public_html</url>
<layout>default</layout> <layout>default</layout>
</repository> </repository>
<site> <site>
<id>parabot-maven</id> <id>parabot-maven</id>
<name>Frontend Parabot Maven</name> <name>Frontend Parabot Maven</name>
<url>ftp://maven.parabot.org/docs/${artifactId}/</url> <url>ftp://maven.parabot.org/public_html/docs/${artifactId}/</url>
</site> </site>
</distributionManagement> </distributionManagement>
+12 -17
View File
@@ -11,32 +11,31 @@ import org.parabot.core.network.proxy.ProxyType;
import org.parabot.core.ui.BotUI; import org.parabot.core.ui.BotUI;
import org.parabot.core.ui.ServerSelector; import org.parabot.core.ui.ServerSelector;
import org.parabot.core.ui.utils.UILog; import org.parabot.core.ui.utils.UILog;
import org.parabot.environment.handlers.exceptions.ExceptionHandler;
import org.parabot.environment.handlers.exceptions.FileExceptionHandler;
import javax.swing.*; import javax.swing.*;
import java.io.File; import java.io.File;
import java.io.IOException;
/** /**
* Parabot v2.7
*
* @author Everel, JKetelaar, Matt, Dane * @author Everel, JKetelaar, Matt, Dane
* @version 2.7 * @version 2.8.1
* @see <a href="http://www.parabot.org">Homepage</a> * @see <a href="https://www.parabot.org">Homepage</a>
*/ */
public final class Landing { public final class Landing {
private static String username; private static String username;
private static String password; private static String password;
public static void main(String... args) throws IOException { public static void main(String... args) {
Thread.setDefaultUncaughtExceptionHandler(new FileExceptionHandler(ExceptionHandler.ExceptionType.CLIENT));
if (Context.getJavaVersion() >= 9) { if (Context.getJavaVersion() >= 9) {
UILog.log("Parabot", "Parabot doesn't support Java 9+ currently. Please downgrade to Java 8 to ensure Parabot is working correctly."); UILog.log("Parabot", "Parabot doesn't support Java 9+ currently. Please downgrade to Java 8 to ensure Parabot is working correctly.");
System.exit(0);
} }
if (!System.getProperty("os.arch").contains("64")) { if (!System.getProperty("os.arch").contains("64")) {
UILog.log("Parabot", "You are not running a 64-bit version of Java, this might cause the client to lag or crash unexpectedly.\r\n" + UILog.log("Parabot", "You are not running a 64-bit version of Java, this might cause the client to lag or crash unexpectedly.\r\n" +
"It's recommended to upgrade to a 64-bit version."); "It is recommended to upgrade to a 64-bit version.");
} }
parseArgs(args); parseArgs(args);
@@ -80,11 +79,11 @@ public final class Landing {
switch (arg.toLowerCase()) { switch (arg.toLowerCase()) {
case "-createdirs": case "-createdirs":
Directories.validate(); Directories.validate();
System.out System.out.println(TranslationHelper.translate(("DIRECTORIES_CREATED")));
.println(TranslationHelper.translate(("DIRECTORIES_CREATED")));
System.exit(0); System.exit(0);
break; break;
case "-debug": case "-debug":
Core.setDump(true);
case "-offlinemode": case "-offlinemode":
Core.setDebug(true); Core.setDebug(true);
break; break;
@@ -130,14 +129,9 @@ public final class Landing {
break; break;
case "-proxy": case "-proxy":
ProxyType type = ProxyType.valueOf(args[++i].toUpperCase()); ProxyType type = ProxyType.valueOf(args[++i].toUpperCase());
if (type == null) { ProxySocket.setProxy(type, args[++i], Integer.parseInt(args[++i]));
System.err.println(TranslationHelper.translate("INVALID_PROXY_TYPE") + args[i]);
System.exit(1);
return;
}
ProxySocket.setProxy(type, args[++i],
Integer.parseInt(args[++i]));
break; break;
case "-proxy_auth":
case "-auth": case "-auth":
ProxySocket.auth = true; ProxySocket.auth = true;
ProxySocket.setLogin(args[++i], args[++i]); ProxySocket.setLogin(args[++i], args[++i]);
@@ -146,6 +140,7 @@ public final class Landing {
Core.disableSec(); Core.disableSec();
break; break;
case "-no_validation": case "-no_validation":
case "-ignore_updates":
Core.disableValidation(); Core.disableValidation();
break; break;
case "-uuid": case "-uuid":
@@ -18,9 +18,10 @@ public abstract class ScriptParser {
public static final Map<ScriptDescription, ScriptExecuter> SCRIPT_CACHE = new HashMap<>(); public static final Map<ScriptDescription, ScriptExecuter> SCRIPT_CACHE = new HashMap<>();
private static final ArrayList<ScriptParser> parsers = new ArrayList<>();
public static ScriptDescription[] getDescriptions() { public static ScriptDescription[] getDescriptions() {
SCRIPT_CACHE.clear(); SCRIPT_CACHE.clear();
final ArrayList<ScriptParser> parsers = new ArrayList<>();
if (Core.inLoadLocal()) { if (Core.inLoadLocal()) {
parsers.add(new LocalJavaScripts()); parsers.add(new LocalJavaScripts());
parsers.add(new BDNScripts()); parsers.add(new BDNScripts());
@@ -47,6 +48,9 @@ public abstract class ScriptParser {
return SORTED_SCRIPT_CACHE.keySet().toArray(new ScriptDescription[SORTED_SCRIPT_CACHE.size()]); return SORTED_SCRIPT_CACHE.keySet().toArray(new ScriptDescription[SORTED_SCRIPT_CACHE.size()]);
} }
public abstract void execute(); public static final void addParser(ScriptParser parser) {
parsers.add(parser);
}
public abstract void execute();
} }
@@ -39,4 +39,9 @@ public class UILog {
return JOptionPane.showOptionDialog(null, message, title, return JOptionPane.showOptionDialog(null, message, title,
JOptionPane.YES_NO_CANCEL_OPTION, messageType, null, options, null); JOptionPane.YES_NO_CANCEL_OPTION, messageType, null, options, null);
} }
public static int alert(final String title, final String message, Object[] options, int initialValue, int messageType) {
return JOptionPane.showOptionDialog(null, message, title,
JOptionPane.YES_NO_CANCEL_OPTION, messageType, null, options, initialValue);
}
} }
@@ -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;
}
}
}
@@ -0,0 +1,139 @@
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 {
/**
* The default index of all options to be selected when the popup appears
*/
private static final int defaultOptionIndex = 1;
/**
* Directory where the reports get written to
*/
private final File reportsDirectory;
/**
* All possible options to select when the popup appears
*/
private final Object[] options = new Object[]{
"Close",
"Open report",
"Ignore " + this.getExceptionType().getName().toLowerCase() + " errors" };
/**
* 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-" + this.getExceptionType().getName().toLowerCase() + "-" + (System.currentTimeMillis() / 1000) + ".txt");
try {
report.createNewFile();
StringBuilder reportContent = new StringBuilder();
reportContent.append("Message: ").append(e.getMessage()).append("\n\n");
reportContent.append(e.toString()).append("\n\n");
for (int i = 0; i < e.getStackTrace().length; i++) {
if (i > 0) {
reportContent.append("\t");
}
reportContent.append(e.getStackTrace()[i]).append("\n");
}
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.",
this.options,
defaultOptionIndex,
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();
}
}
}
}
}
}
@@ -77,7 +77,7 @@ public abstract class ServerProvider implements Opcodes {
return; return;
} }
HookParser parser = hookFile.getParser(); HookParser parser = hookFile.getParser();
Injectable[] injectables = parser.getInjectables(); Injectable[] injectables = parser.getInjectables();
if (injectables == null) { if (injectables == null) {
@@ -92,17 +92,18 @@ public abstract class ServerProvider implements Opcodes {
index++; index++;
} }
} catch (NullPointerException ex) { } catch (NullPointerException ex) {
if(!crashed) { if (!crashed) {
Injectable inj = injectables[index]; Injectable inj = injectables[index];
int resp = UILog.alert("Outdated client", "This server currently has outdated hooks, please report it to a member of the Parabot staff.\r\n\r\n" + int resp = UILog.alert("Outdated client", "This server currently has outdated hooks, please report it to the Parabot staff.\r\n\r\n" +
"Broken hook:\r\n"+inj, new Object[]{"Close", "Report here..."}, JOptionPane.ERROR_MESSAGE); "Broken hook:\r\n" + inj, new Object[]{ "Close", "Report here..." }, JOptionPane.ERROR_MESSAGE);
if(resp == 1) { if (resp == 1) {
URI uri = URI.create(Configuration.COMMUNITY_PAGE + "forum/135-reports/"); URI uri = URI.create(Configuration.COMMUNITY_PAGE + "forum/135-reports/");
try { try {
Desktop.getDesktop().browse(uri); Desktop.getDesktop().browse(uri);
} catch (IOException ignore) {} } catch (IOException ignore) {
}
} }
} }
crashed = true; crashed = true;
@@ -112,20 +113,6 @@ public abstract class ServerProvider implements Opcodes {
Context.getInstance().setHookParser(parser); Context.getInstance().setHookParser(parser);
} }
private HookFile fetchHookFile() {
HookFile hookFile = getHookFile();
if (hookFile != null) {
return hookFile;
}
URL hookLocation = getHooks();
if (hookLocation == null) {
return null;
}
return new HookFile(hookLocation, HookFile.TYPE_XML);
}
/** /**
* Add custom items to the bot menu bar * Add custom items to the bot menu bar
* *
@@ -156,16 +143,16 @@ public abstract class ServerProvider implements Opcodes {
public void initMouse() { public void initMouse() {
final Context context = Context.getInstance(); final Context context = Context.getInstance();
final Applet applet = context.getApplet(); final Applet applet = context.getApplet();
final Mouse mouse = new Mouse(applet); final Mouse mouse = new Mouse(applet);
applet.addMouseListener(mouse); applet.addMouseListener(mouse);
applet.addMouseMotionListener(mouse); applet.addMouseMotionListener(mouse);
context.setMouse(mouse); context.setMouse(mouse);
} }
public void initKeyboard() { public void initKeyboard() {
final Context context = Context.getInstance(); final Context context = Context.getInstance();
final Applet applet = context.getApplet(); final Applet applet = context.getApplet();
final Keyboard keyboard = new Keyboard(applet); final Keyboard keyboard = new Keyboard(applet);
applet.addKeyListener(keyboard); applet.addKeyListener(keyboard);
context.setKeyboard(keyboard); context.setKeyboard(keyboard);
@@ -191,4 +178,18 @@ public abstract class ServerProvider implements Opcodes {
} }
private HookFile fetchHookFile() {
HookFile hookFile = getHookFile();
if (hookFile != null) {
return hookFile;
}
URL hookLocation = getHooks();
if (hookLocation == null) {
return null;
}
return new HookFile(hookLocation, HookFile.TYPE_XML);
}
} }
@@ -24,9 +24,9 @@ import java.net.URL;
* @author JKetelaar * @author JKetelaar
*/ */
public class LocalPublicServerExecuter extends ServerExecuter { public class LocalPublicServerExecuter extends ServerExecuter {
private String serverName; private String serverName;
private String serverUrl; private String serverUrl;
private String providerUrl; private String providerUrl;
private ServerProviderInfo serverProviderInfo; private ServerProviderInfo serverProviderInfo;
public LocalPublicServerExecuter(final String serverName, final ServerProviderInfo serverProviderInfo, String serverUrl, String providerUrl) { public LocalPublicServerExecuter(final String serverName, final ServerProviderInfo serverProviderInfo, String serverUrl, String providerUrl) {
@@ -80,8 +80,8 @@ public class LocalPublicServerExecuter extends ServerExecuter {
BuildPath.add(destination.toURI().toURL()); BuildPath.add(destination.toURI().toURL());
ServerLoader serverLoader = new ServerLoader(classPath); ServerLoader serverLoader = new ServerLoader(classPath);
final String[] classNames = serverLoader.getServerClassNames(); final String[] classNames = serverLoader.getServerClassNames();
if (classNames.length == 0) { if (classNames.length == 0) {
UILog.log( UILog.log(
"Error", "Error",
@@ -16,8 +16,8 @@ import java.net.MalformedURLException;
*/ */
public class LocalServerExecuter extends ServerExecuter { public class LocalServerExecuter extends ServerExecuter {
private final Constructor<?> serverProviderConstructor; private final Constructor<?> serverProviderConstructor;
private ClassPath classPath; private ClassPath classPath;
private String serverName; private String serverName;
public LocalServerExecuter(Constructor<?> serverProviderConstructor, public LocalServerExecuter(Constructor<?> serverProviderConstructor,
ClassPath classPath, final String serverName) { ClassPath classPath, final String serverName) {
@@ -36,10 +36,9 @@ public class PublicServerExecuter extends ServerExecuter {
} }
}; };
private String serverName;
private PBLocalPreferences settings;
private final String cacheVersionKey = "cachedProviderVersion"; private final String cacheVersionKey = "cachedProviderVersion";
private String serverName;
private PBLocalPreferences settings;
public PublicServerExecuter(final String serverName) { public PublicServerExecuter(final String serverName) {
this.serverName = serverName; this.serverName = serverName;
@@ -58,14 +57,14 @@ public class PublicServerExecuter extends ServerExecuter {
Core.verbose("Downloading: " + jarUrl + " ..."); Core.verbose("Downloading: " + jarUrl + " ...");
String providerVersion = serverProviderInfo.getProviderVersion(); String providerVersion = serverProviderInfo.getProviderVersion();
if(providerVersion == null) { if (providerVersion == null) {
providerVersion = "error"; providerVersion = "error";
} }
settings = new PBLocalPreferences(serverProviderInfo.getClientCRC32()+".json"); settings = new PBLocalPreferences(serverProviderInfo.getClientCRC32() + ".json");
if(settings.getSetting(cacheVersionKey) != null) { if (settings.getSetting(cacheVersionKey) != null) {
Core.verbose(String.format("Latest provider version: %s, local provider version: %s", settings.getSetting(cacheVersionKey), providerVersion)); Core.verbose(String.format("Latest provider version: %s, local provider version: %s", settings.getSetting(cacheVersionKey), providerVersion));
if(!settings.getSetting(cacheVersionKey).equals(providerVersion)) { if (!settings.getSetting(cacheVersionKey).equals(providerVersion)) {
Core.verbose("Local provider outdated, clearing cache."); Core.verbose("Local provider outdated, clearing cache.");
Directories.clearCache(); Directories.clearCache();
} }
@@ -88,8 +87,8 @@ public class PublicServerExecuter extends ServerExecuter {
BuildPath.add(destination.toURI().toURL()); BuildPath.add(destination.toURI().toURL());
ServerLoader serverLoader = new ServerLoader(classPath); ServerLoader serverLoader = new ServerLoader(classPath);
final String[] classNames = serverLoader.getServerClassNames(); final String[] classNames = serverLoader.getServerClassNames();
if (classNames == null || classNames.length == 0) { if (classNames == null || classNames.length == 0) {
UILog.log( UILog.log(
"Error", "Error",
@@ -3,6 +3,8 @@ package org.parabot.environment.servers.executers;
import org.parabot.core.Context; import org.parabot.core.Context;
import org.parabot.core.ui.BotUI; import org.parabot.core.ui.BotUI;
import org.parabot.core.ui.components.PaintComponent; 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; import org.parabot.environment.servers.ServerProvider;
/** /**
@@ -15,7 +17,7 @@ public abstract class ServerExecuter {
public abstract void run(); public abstract void run();
public void finalize(final ServerProvider provider, final String serverName) { public void finalize(final ServerProvider provider, final String serverName) {
new Thread(new Runnable() { Thread serverThread = new Thread(new Runnable() {
@Override @Override
public void run() { public void run() {
try { try {
@@ -30,7 +32,11 @@ public abstract class ServerExecuter {
t.printStackTrace(); t.printStackTrace();
} }
} }
}).start(); });
serverThread.setUncaughtExceptionHandler(new FileExceptionHandler(ExceptionHandler.ExceptionType.SERVER));
serverThread.start();
} }
} }
@@ -29,8 +29,9 @@ public class ServerLoader extends ASMClassLoader {
public final String[] getServerClassNames() { public final String[] getServerClassNames() {
final List<String> classNames = new ArrayList<String>(); final List<String> classNames = new ArrayList<String>();
for (ClassNode c : classPath.classes.values()) { for (ClassNode c : classPath.classes.values()) {
if (c.superName.replace('/', '.').equals( if (c.superName
ServerProvider.class.getName())) { .replace('/', '.')
.equals(ServerProvider.class.getName())) {
classNames.add(c.name.replace('/', '.')); classNames.add(c.name.replace('/', '.'));
} }
} }
@@ -0,0 +1,39 @@
package org.parabot;
import org.junit.Test;
import org.parabot.environment.handlers.exceptions.ExceptionHandler;
import org.parabot.environment.handlers.exceptions.FileExceptionHandler;
public class FileExceptionHandlerTest {
@Test
public void manualTest() {
FileExceptionHandler handler = new FileExceptionHandler(ExceptionHandler.ExceptionType.CLIENT);
handler.setIgnored(true);
Exception exception = new NullPointerException("Manual test");
handler.handle(exception);
}
@Test
public void threadHandlerTest() {
FileExceptionHandler handler = new FileExceptionHandler(ExceptionHandler.ExceptionType.CLIENT);
handler.setIgnored(true);
Thread thread = new Thread() {
@Override
public void run() throws NullPointerException {
throw new NullPointerException("Thread test");
}
};
thread.setUncaughtExceptionHandler(handler);
thread.start();
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}