diff --git a/add-on/src/addon/javascript/background.js b/add-on/src/addon/javascript/background.js index 31b8cbf..f886f84 100644 --- a/add-on/src/addon/javascript/background.js +++ b/add-on/src/addon/javascript/background.js @@ -125,6 +125,18 @@ var listeners = [function (message, returnValue) { case "CommandQuit": returnValue = quit(); break; + case "CommandError": + console.error("Error message", message); + if (message.forward) + { + returnValue = queryTab(message); + } + else + { + throw new TypeError("This is a background test"); + returnValue = false; + } + break; } } else if (message == "quit") @@ -202,7 +214,19 @@ interfacePort.onMessage.addListener((message) => { console.log("Sending ", returnValue) interfacePort.postMessage(returnValue); }).catch(error => { - console.error("Error in evaluating message: ", error) +// console.error("Error in evaluating message: ", error); + interfacePort.postMessage({ + "messageNum": message.messageNum, + "message": { + "type": "edu.regis.universeplayer.browserCommands.CommandReturn", + "returnValue": null, + "confirmation": { + type: "edu.regis.universeplayer.browserCommands.CommandConfirmation", + message: "Error in executing request", + errorCode: JSON.parse(JSON.stringify(error, Object.getOwnPropertyNames(error))) + } + } + }); }); }); diff --git a/add-on/src/addon/javascript/foreground.js b/add-on/src/addon/javascript/foreground.js index db6ec11..6f95dbe 100644 --- a/add-on/src/addon/javascript/foreground.js +++ b/add-on/src/addon/javascript/foreground.js @@ -30,6 +30,9 @@ function handleMessage(message) return false; case "CommandSeek": return seek(message.time); + case "CommandError": + throw new TypeError("This is a foreground test"); + return false; } } } diff --git a/addonInter/src/main/java/edu/regis/universeplayer/addon/BrowserLink.java b/addonInter/src/main/java/edu/regis/universeplayer/addon/BrowserLink.java index f9eee2e..9164711 100644 --- a/addonInter/src/main/java/edu/regis/universeplayer/addon/BrowserLink.java +++ b/addonInter/src/main/java/edu/regis/universeplayer/addon/BrowserLink.java @@ -5,6 +5,8 @@ package edu.regis.universeplayer.addon; import com.google.gson.*; + +import edu.regis.universeplayer.browserCommands.CommandConfirmation; import edu.regis.universeplayer.browserCommands.MessageRunner; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -266,4 +268,10 @@ public class BrowserLink extends MessageRunner } return returnValue; } + + @Override + public Object getErrorObject(Throwable e) + { + return new CommandConfirmation(e); + } } diff --git a/addonInter/src/main/java/edu/regis/universeplayer/addon/ThrowableSerializer.java b/addonInter/src/main/java/edu/regis/universeplayer/addon/ThrowableSerializer.java index b949496..9c58c52 100644 --- a/addonInter/src/main/java/edu/regis/universeplayer/addon/ThrowableSerializer.java +++ b/addonInter/src/main/java/edu/regis/universeplayer/addon/ThrowableSerializer.java @@ -21,7 +21,7 @@ public class ThrowableSerializer implements JsonSerializer, JsonDeser { stack.add(context.serialize(trace)); } - ob.add("trace", stack); + ob.add("stack", stack); JsonArray suppressed = new JsonArray(); for (Throwable throwable: src.getSuppressed()) { @@ -35,19 +35,37 @@ public class ThrowableSerializer implements JsonSerializer, JsonDeser public Throwable deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { JsonObject ob = json.getAsJsonObject(); - Throwable throwable = new Throwable(ob.get("message").getAsString(), context.deserialize(ob.get("cause"), typeOfT)); - throwable.initCause(context.deserialize(ob.get("cause"), Throwable.class)); - JsonArray traceJson = ob.getAsJsonArray("trace"); - StackTraceElement[] trace = new StackTraceElement[traceJson.size()]; - for (int i = 0; i < trace.length; i++) + Throwable cause = context.deserialize(ob.get("cause"), typeOfT); + Throwable throwable; + if (cause == null) { - trace[i] = context.deserialize(traceJson.get(i), StackTraceElement.class); + throwable = new Throwable(ob.get("message").getAsString()); } - throwable.setStackTrace(trace); - JsonArray suppressed = ob.getAsJsonArray("suppressed"); - for (JsonElement el: suppressed) + else { - throwable.addSuppressed(context.deserialize(el, Throwable.class)); + throwable = new Throwable(ob.get("message").getAsString(), context + .deserialize(ob.get("cause"), typeOfT)); + throwable.initCause(context.deserialize(ob.get("cause"), Throwable.class)); + } + JsonArray traceJson = ob.getAsJsonArray("stack"); + if (traceJson != null) + { + StackTraceElement[] trace = new StackTraceElement[traceJson.size()]; + for (int i = 0; i < trace.length; i++) + { + trace[i] = context + .deserialize(traceJson.get(i), StackTraceElement.class); + } + throwable.setStackTrace(trace); + } + JsonArray suppressed = ob.getAsJsonArray("suppressed"); + if (suppressed != null) + { + for (JsonElement el : suppressed) + { + throwable.addSuppressed(context + .deserialize(el, Throwable.class)); + } } return throwable; } diff --git a/browserCommands/src/main/java/edu/regis/universeplayer/browserCommands/CommandError.java b/browserCommands/src/main/java/edu/regis/universeplayer/browserCommands/CommandError.java new file mode 100644 index 0000000..4a1242d --- /dev/null +++ b/browserCommands/src/main/java/edu/regis/universeplayer/browserCommands/CommandError.java @@ -0,0 +1,44 @@ +package edu.regis.universeplayer.browserCommands; + +/** + * A debugging command that instructs the browser to throw an error. + */ +public class CommandError implements BrowserCommand +{ + private boolean forward; + + /** + * Creates an error command. + * + * @param forward - Determines where the error should be thrown. If true, it + * will be thrown from the website tab. If false, it will be + * thrown by the addon background script. + */ + public CommandError(boolean forward) + { + this.forward = forward; + } + + /** + * Checks whether the error will be thrown from the foreground or background + * script. + * + * @return True if the error will be thrown from the foreground script, + * false if it will be thrown from the background. + */ + public boolean isForeground() + { + return this.forward; + } + + /** + * Obtains the name of the command. + * + * @return The command name. + */ + @Override + public String getCommandName() + { + return "error"; + } +} diff --git a/browserCommands/src/main/java/edu/regis/universeplayer/browserCommands/MessageHandler.java b/browserCommands/src/main/java/edu/regis/universeplayer/browserCommands/MessageHandler.java index 5b79439..5ae3457 100644 --- a/browserCommands/src/main/java/edu/regis/universeplayer/browserCommands/MessageHandler.java +++ b/browserCommands/src/main/java/edu/regis/universeplayer/browserCommands/MessageHandler.java @@ -21,17 +21,17 @@ import java.util.concurrent.Future; public class MessageHandler implements Runnable, MessageSerializer { private final Logger logger; - + public final String name; - + private final InputStream input; private final OutputStream output; - + private final ExecutorService executor; private final LinkedList listeners = new LinkedList<>(); protected final HashMap> messageResponses = new HashMap<>(); protected final Queue updates = new LinkedList<>(); - + /** * Creates a message handler. * @@ -47,13 +47,13 @@ public class MessageHandler implements Runnable, MessageSerializer this.output = output; this.executor = Executors.newCachedThreadPool(); } - + @Override public Logger getLogger() { return logger; } - + /** * Called at the beginning of every loop to do extra processing and check to see if we can still run. * @@ -63,13 +63,13 @@ public class MessageHandler implements Runnable, MessageSerializer { return false; } - + @Override public void run() { BufferedInputStream browserIn = null; BufferedOutputStream browserOut = null; - + byte[][] message; byte[] messageByte; Object messageOb; @@ -77,12 +77,12 @@ public class MessageHandler implements Runnable, MessageSerializer ByteBuffer numBuffer = ByteBuffer.allocate(4); HashSet toRemove = new HashSet<>(); boolean running = true; - + try { browserIn = new BufferedInputStream(this.input); browserOut = new BufferedOutputStream(this.output); - + while (!this.onRun() && running) { try @@ -99,22 +99,32 @@ public class MessageHandler implements Runnable, MessageSerializer { numBuffer.clear(); numBuffer.put(message[0]); - messageOb = this.deserializeObject(message[1]); - messageResponse = this.triggerListeners(messageOb); + try + { + messageOb = this.deserializeObject(message[1]); + } + catch (IOException | ClassNotFoundException e) + { + logger.error("Could not read message", e); + messageOb = this.getErrorObject(e); + } + messageResponse = this + .triggerListeners(messageOb); synchronized (this.messageResponses) { numBuffer.clear(); - this.messageResponses.put(numBuffer.getInt(), messageResponse); + this.messageResponses.put(numBuffer + .getInt(), messageResponse); } } } } - catch (IOException | ClassNotFoundException e) + catch (IOException e) { logger.error("Could not retrieve message", e); running = false; } - + /* * Returns any finished responses. */ @@ -143,7 +153,7 @@ public class MessageHandler implements Runnable, MessageSerializer toRemove.clear(); } } - + /* * Send in any updates */ @@ -218,14 +228,14 @@ public class MessageHandler implements Runnable, MessageSerializer } } } - + /** * Callback for when closing the application. */ protected void onClose() { } - + /** * Triggers the message listeners. * @@ -254,7 +264,7 @@ public class MessageHandler implements Runnable, MessageSerializer }); return returnVal; } - + /** * Adds a message listener * @@ -268,7 +278,7 @@ public class MessageHandler implements Runnable, MessageSerializer this.listeners.add(listener); } } - + /** * Sends an object through the handler as an update not associated with any message. * @@ -281,7 +291,7 @@ public class MessageHandler implements Runnable, MessageSerializer this.updates.add(object); } } - + /** * Checks to see if a listener has been added to the list. * @@ -295,7 +305,7 @@ public class MessageHandler implements Runnable, MessageSerializer return this.listeners.contains(listener); } } - + /** * Removes a message listener from the list. * @@ -308,7 +318,7 @@ public class MessageHandler implements Runnable, MessageSerializer this.listeners.remove(listener); } } - + /** * A message listener is called when a message is sent from the remote. * diff --git a/browserCommands/src/main/java/edu/regis/universeplayer/browserCommands/MessageSerializer.java b/browserCommands/src/main/java/edu/regis/universeplayer/browserCommands/MessageSerializer.java index f5fc216..05f92ae 100644 --- a/browserCommands/src/main/java/edu/regis/universeplayer/browserCommands/MessageSerializer.java +++ b/browserCommands/src/main/java/edu/regis/universeplayer/browserCommands/MessageSerializer.java @@ -12,7 +12,7 @@ import java.nio.ByteBuffer; public interface MessageSerializer { Logger getLogger(); - + /** * Converts an object into a form that can be sent. * @@ -31,13 +31,14 @@ public interface MessageSerializer return byteStream.toByteArray(); } } - + /** * Converts a byte stream into an object. * * @param message - The message received. * @return An object. - * @throws IOException If there is an error in parsing the message. + * @throws IOException If there is an error in parsing the + * message. * @throws ClassNotFoundException If the object is not recognized */ default Object deserializeObject(byte[] message) throws IOException, ClassNotFoundException @@ -50,12 +51,13 @@ public interface MessageSerializer } } } - + /** * Writes a message to the output stream. * * @param out - The output stream to write to. - * @param messageNum - The ID of the message being sent. This will help keep track of responses. + * @param messageNum - The ID of the message being sent. This will help keep + * track of responses. * @param message - The actual message contents to write. * @throws IOException Thrown when an exception occures */ @@ -86,7 +88,7 @@ public interface MessageSerializer out.write(message); out.flush(); } - + /** * Reads a message from the input stream * @@ -119,7 +121,8 @@ public interface MessageSerializer readLength = in.read(lengthBuffer.array()); if (readLength == 0) { - getLogger().error("Malformed message, could not get message length."); + getLogger() + .error("Malformed message, could not get message length."); return null; } message = new byte[lengthBuffer.getInt()]; @@ -129,9 +132,21 @@ public interface MessageSerializer readLength = in.read(message); if (readLength < message.length) { - getLogger().warn("Message shorter than reported (expected {} bytes, got {} bytes)", message.length, readLength); + getLogger() + .warn("Message shorter than reported (expected {} bytes, got {} bytes)", message.length, readLength); } getLogger().trace("Reading message"); - return new byte[][] {messageNum, message}; + return new byte[][]{messageNum, message}; + } + + /** + * This method is called when an error in deserialization occurs. + * + * @param e - The error thrown. + * @return An object to return to listeners in the event of a read error. + */ + default Object getErrorObject(Throwable e) + { + return e; } } diff --git a/interface/src/main/java/edu/regis/universeplayer/browser/BrowserPlayer.java b/interface/src/main/java/edu/regis/universeplayer/browser/BrowserPlayer.java index 066ab62..be41ccb 100644 --- a/interface/src/main/java/edu/regis/universeplayer/browser/BrowserPlayer.java +++ b/interface/src/main/java/edu/regis/universeplayer/browser/BrowserPlayer.java @@ -12,6 +12,7 @@ import edu.regis.universeplayer.browserCommands.*; import edu.regis.universeplayer.data.InternetSong; import edu.regis.universeplayer.data.PlaybackEvent; import edu.regis.universeplayer.data.Song; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -28,14 +29,15 @@ import java.util.concurrent.*; */ public class BrowserPlayer implements Player, UpdateListener { - private static final Logger logger = LoggerFactory.getLogger(BrowserPlayer.class); - + private static final Logger logger = LoggerFactory + .getLogger(BrowserPlayer.class); + private final LinkedList listeners = new LinkedList<>(); - + private InternetSong currentSong; - + private Browser browserRef = null; - + private Browser getBrowser() { if (browserRef == null) @@ -56,19 +58,20 @@ public class BrowserPlayer implements Player, UpdateListener } return browserRef; } - + @Override public Song getCurrentSong() { return this.currentSong; } - + @Override public QueryFuture loadSong(InternetSong song) { try { - return new ForwardedFuture(getBrowser().sendObject(new CommandLoadSong(song.location))); + return new ForwardedFuture(getBrowser() + .sendObject(new CommandLoadSong(song.location))); } catch (IOException e) { @@ -76,7 +79,7 @@ public class BrowserPlayer implements Player, UpdateListener return null; } } - + /** * Tells the browser process to shut down. */ @@ -85,7 +88,8 @@ public class BrowserPlayer implements Player, UpdateListener { try { - QueryFuture future = new ForwardedFuture(getBrowser().sendObject(new CommandQuit())); + QueryFuture future = new ForwardedFuture(getBrowser() + .sendObject(new CommandQuit())); return future; } catch (IOException e) @@ -94,7 +98,7 @@ public class BrowserPlayer implements Player, UpdateListener return null; } } - + /** * Adds a listener for playback status updates. * @@ -105,7 +109,7 @@ public class BrowserPlayer implements Player, UpdateListener { this.listeners.add(listener); } - + /** * Checks to see if a listener has been added. * @@ -117,7 +121,7 @@ public class BrowserPlayer implements Player, UpdateListener { return this.listeners.contains(listener); } - + /** * Removes a listener for playback status updates. * @@ -128,13 +132,14 @@ public class BrowserPlayer implements Player, UpdateListener { this.listeners.remove(listener); } - + @Override public QueryFuture play() { try { - return new ForwardedFuture(getBrowser().sendObject(new CommandSetPlayback(CommandSetPlayback.Playback.PLAY))); + return new ForwardedFuture(getBrowser() + .sendObject(new CommandSetPlayback(CommandSetPlayback.Playback.PLAY))); } catch (IOException e) { @@ -142,13 +147,14 @@ public class BrowserPlayer implements Player, UpdateListener return null; } } - + @Override public QueryFuture pause() { try { - return new ForwardedFuture(getBrowser().sendObject(new CommandSetPlayback(CommandSetPlayback.Playback.PAUSE))); + return new ForwardedFuture(getBrowser() + .sendObject(new CommandSetPlayback(CommandSetPlayback.Playback.PAUSE))); } catch (IOException e) { @@ -156,13 +162,14 @@ public class BrowserPlayer implements Player, UpdateListener return null; } } - + @Override public QueryFuture togglePlayback() { try { - return new ForwardedFuture(getBrowser().sendObject(new CommandSetPlayback(CommandSetPlayback.Playback.PAUSE))); + return new ForwardedFuture(getBrowser() + .sendObject(new CommandSetPlayback(CommandSetPlayback.Playback.PAUSE))); } catch (IOException e) { @@ -170,7 +177,7 @@ public class BrowserPlayer implements Player, UpdateListener return null; } } - + /** * Stops playback of the current song. */ @@ -179,7 +186,8 @@ public class BrowserPlayer implements Player, UpdateListener { try { - return new ForwardedFuture(getBrowser().sendObject(new CommandLoadSong((URL) null))); + return new ForwardedFuture(getBrowser() + .sendObject(new CommandLoadSong((URL) null))); } catch (IOException e) { @@ -187,13 +195,14 @@ public class BrowserPlayer implements Player, UpdateListener return null; } } - + @Override public QueryFuture seek(float time) { try { - return new ForwardedFuture(getBrowser().sendObject(new CommandSeek(time))); + return new ForwardedFuture(getBrowser() + .sendObject(new CommandSeek(time))); } catch (IOException e) { @@ -201,7 +210,7 @@ public class BrowserPlayer implements Player, UpdateListener return null; } } - + /** * Obtains the player's current playback status. * @@ -215,54 +224,54 @@ public class BrowserPlayer implements Player, UpdateListener Future future = getBrowser().sendObject(new QueryStatus()); return new QueryFuture<>() { - + private CommandReturn getVal() throws ExecutionException, InterruptedException { return ((CommandReturn) future.get()); } - + private CommandReturn getVal(long timeout, TimeUnit unit) throws ExecutionException, InterruptedException, TimeoutException { return ((CommandReturn) future.get(timeout, unit)); } - + @Override public CommandConfirmation getConfirmation() throws CancellationException, ExecutionException, InterruptedException { return this.getVal().getConfirmation(); } - + @Override public CommandConfirmation getConfirmation(long timeout, TimeUnit unit) throws ExecutionException, InterruptedException, TimeoutException { return this.getVal(timeout, unit).getConfirmation(); } - + @Override public boolean cancel(boolean mayInterruptIfRunning) { return future.cancel(mayInterruptIfRunning); } - + @Override public boolean isCancelled() { return future.isCancelled(); } - + @Override public boolean isDone() { return future.isDone(); } - + @Override public PlaybackStatus get() throws InterruptedException, ExecutionException { String value = getVal().getReturnValue(); return PlaybackStatus.valueOf(value); } - + @Override public PlaybackStatus get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { @@ -277,19 +286,40 @@ public class BrowserPlayer implements Player, UpdateListener return null; } } - + @Override public QueryFuture getCurrentTime() { return null; } - + @Override public QueryFuture getLength() { return null; } - + + /** + * Causes the browser to throw an error + * + * @param forward - Whether the error should be thrown from the foreground + * script or the background script. + * @return + */ + public QueryFuture throwError(boolean forward) + { + try + { + return new ForwardedFuture(getBrowser() + .sendObject(new CommandError(forward))); + } + catch (IOException e) + { + logger.error("Could not send message", e); + return null; + } + } + @Override public void onUpdate(Object object, MessageRunner runner) { @@ -300,62 +330,62 @@ public class BrowserPlayer implements Player, UpdateListener this.listeners.forEach(l -> l.onPlaybackChanged(status)); } } - + private class ForwardedFuture implements QueryFuture { private final Future future; - + ForwardedFuture(Future future) { this.future = future; } - + private CommandReturn getVal() throws ExecutionException, InterruptedException { return ((CommandReturn) this.future.get()); } - + private CommandReturn getVal(long timeout, TimeUnit unit) throws ExecutionException, InterruptedException, TimeoutException { return ((CommandReturn) this.future.get(timeout, unit)); } - + @Override public CommandConfirmation getConfirmation() throws CancellationException, ExecutionException, InterruptedException { return this.getVal().getConfirmation(); } - + @Override public CommandConfirmation getConfirmation(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { return this.getVal(timeout, unit).getConfirmation(); } - + @Override public boolean cancel(boolean mayInterruptIfRunning) { return this.future.cancel(mayInterruptIfRunning); } - + @Override public boolean isCancelled() { return this.future.isCancelled(); } - + @Override public boolean isDone() { return this.future.isDone(); } - + @Override public T get() throws InterruptedException, ExecutionException { return this.getVal().getReturnValue(); } - + @Override public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { diff --git a/interface/src/main/java/edu/regis/universeplayer/player/Interface.java b/interface/src/main/java/edu/regis/universeplayer/player/Interface.java index ab72826..0f91453 100644 --- a/interface/src/main/java/edu/regis/universeplayer/player/Interface.java +++ b/interface/src/main/java/edu/regis/universeplayer/player/Interface.java @@ -7,6 +7,8 @@ package edu.regis.universeplayer.player; import edu.regis.universeplayer.Player; import edu.regis.universeplayer.browser.Browser; import edu.regis.universeplayer.browser.BrowserPlayer; +import edu.regis.universeplayer.browserCommands.CommandError; +import edu.regis.universeplayer.browserCommands.QueryFuture; import edu.regis.universeplayer.data.InternetSong; import edu.regis.universeplayer.data.Queue; import edu.regis.universeplayer.data.*; @@ -28,9 +30,11 @@ import java.util.Collection; import java.util.Locale; import java.util.ResourceBundle; import java.util.Set; +import java.util.concurrent.ExecutionException; /** - * The Interface class serves as the primary GUI that the player interacts with. + * The Interface class serves as the primary GUI that the player interacts + * with. * * @author William Hubbard * @version 0.1 @@ -40,7 +44,8 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL // static { // Locale.setDefault(new Locale("es", "ES")); // } - private static final Logger logger = LoggerFactory.getLogger(Interface.class); + private static final Logger logger = LoggerFactory + .getLogger(Interface.class); private static final ResourceBundle langs = ResourceBundle .getBundle("lang.interface", Locale.getDefault()); @@ -119,8 +124,10 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL inter.players.add(browserPlayer = new BrowserPlayer()); logger.debug("Sending ping"); browser.sendObject("ping"); - Runtime.getRuntime().addShutdownHook(new Thread(browserPlayer::close)); - Player.REGISTERED_PLAYERS.put(InternetSong.class, browserPlayer); + Runtime.getRuntime() + .addShutdownHook(new Thread(browserPlayer::close)); + Player.REGISTERED_PLAYERS + .put(InternetSong.class, browserPlayer); // LinkedList> pingRequests = new LinkedList<>(); // for (int i = 0; i < 20; i++) @@ -155,7 +162,8 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL { logger.error("Could not open browser background", e); JOptionPane - .showMessageDialog(inter != null && inter.isVisible() ? inter : null, e, langs + .showMessageDialog(inter != null && inter + .isVisible() ? inter : null, e, langs .getString("error.generic"), JOptionPane.ERROR_MESSAGE); } } @@ -221,7 +229,8 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL { if (!commDir.getParentFile().mkdir()) { - logger.error("Could not create shared directory {}", commDir.getParent()); + logger.error("Could not create shared directory {}", commDir + .getParent()); } } if (!commDir.exists()) @@ -262,7 +271,8 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL { AbstractAction action; - this.actions.put("refresh", action = new AbstractAction(langs.getString("actions.refresh")) + this.actions.put("refresh", action = new AbstractAction(langs + .getString("actions.refresh")) { @Override public void actionPerformed(ActionEvent e) @@ -302,7 +312,8 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL }); action.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_E); - this.actions.put("exit", action = new AbstractAction(langs.getString("actions.exit")) + this.actions.put("exit", action = new AbstractAction(langs + .getString("actions.exit")) { @Override public void actionPerformed(ActionEvent e) @@ -316,7 +327,8 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL }); action.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_X); - this.actions.put("logs", action = new AbstractAction(langs.getString("actions.logs")) + this.actions.put("logs", action = new AbstractAction(langs + .getString("actions.logs")) { @Override public void actionPerformed(ActionEvent e) @@ -330,9 +342,114 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL } } }); - action.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_F12, 0)); + action.putValue(Action.ACCELERATOR_KEY, KeyStroke + .getKeyStroke(KeyEvent.VK_F12, 0)); - this.actions.put("about", action = new AbstractAction(langs.getString("actions.about")) + this.actions.put("debug.error", action = + new AbstractAction(langs.getString("actions.debug.error")) + { + @Override + public void actionPerformed(ActionEvent e) + { + if (this.isEnabled()) + { + new SwingWorker() + { + /** + * Computes + * a + * result, + * or + * throws + * an + * exception + * if + * unable + * to + * do + * so. + * + *

+ * Note + * that + * this + * method + * is + * executed + * only + * once. + * + *

+ * Note: + * this + * method + * is + * executed + * in + * a + * background + * thread. + * + * @return the computed result + * @throws Exception if unable to compute a result + */ + @Override + protected Void doInBackground() throws Exception + { + Interface.this.players.stream() + .filter(p -> p instanceof BrowserPlayer) + .map(p -> (BrowserPlayer) p) + .findFirst() + .ifPresentOrElse(p -> { + logger.debug("Throwing " + + "error"); + QueryFuture command = p + .throwError((false)); + try + { + if (!command + .getConfirmation() + .wasSuccessful()) + { + logger.error("Could not run command", + command.getConfirmation() + .getError()); + JOptionPane + .showMessageDialog(Interface.this, + command.getConfirmation() + .getError(), + command.getConfirmation() + .getMessage(), + JOptionPane.ERROR_MESSAGE); + } + } + catch (ExecutionException | InterruptedException executionException) + { + logger.error("Error" + + " creating " + + "debug " + + "message", executionException); + JOptionPane + .showMessageDialog(Interface.this, executionException + .getMessage(), langs + .getString( + "error.command"), JOptionPane.ERROR_MESSAGE); + } + }, () -> JOptionPane + .showMessageDialog(Interface.this, langs + .getString("error.browser.null"), langs + .getString("error.debug.error"), JOptionPane.ERROR_MESSAGE)); + return null; + } + }.execute(); + } + } + }); + action.putValue(Action.ACCELERATOR_KEY, KeyStroke + .getKeyStroke(KeyEvent.VK_F12, 0)); + + this.actions.put("about", action = new AbstractAction(langs + .getString("actions.about")) { @Override public void actionPerformed(ActionEvent e) @@ -345,7 +462,8 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL }); this.actions - .put("view.all", action = new AbstractAction(langs.getString("actions.view.all")) + .put("view.all", action = new AbstractAction(langs + .getString("actions.view.all")) { @Override public void actionPerformed(ActionEvent e) @@ -521,7 +639,8 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL protected void constructWindow() { JMenuBar toolbar; - JMenu fileMenu, viewMenu, collectionsMenu, playbackMenu, helpMenu; + JMenu fileMenu, viewMenu, collectionsMenu, playbackMenu, helpMenu, + debugMenu; this.setTitle(langs.getString("title")); this.getContentPane().setLayout(new BorderLayout()); @@ -569,6 +688,10 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL helpMenu.setMnemonic(KeyEvent.VK_H); toolbar.add(helpMenu); + debugMenu = new JMenu(langs.getString("menu.debug.title")); + debugMenu.setMnemonic(KeyEvent.VK_D); + helpMenu.add(debugMenu); + fileMenu.add(new JMenuItem(actions.get("refresh"))); fileMenu.add(actions.get("addExternal")); @@ -590,6 +713,9 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL helpMenu.add(new JMenuItem(actions.get("logs"))); + helpMenu.add(debugMenu); + debugMenu.add(new JMenuItem(actions.get("debug.error"))); + helpMenu.add(new JMenuItem(actions.get("about"))); this.setJMenuBar(toolbar); @@ -606,15 +732,18 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL ((SortingFocusTraversalPolicy) this.getFocusTraversalPolicy()) .setImplicitDownCycleTraversal(true); this.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, Set - .of(AWTKeyStroke.getAWTKeyStroke(KeyEvent.VK_DOWN, 0), AWTKeyStroke + .of(AWTKeyStroke + .getAWTKeyStroke(KeyEvent.VK_DOWN, 0), AWTKeyStroke .getAWTKeyStroke(KeyEvent.VK_RIGHT, 0))); this.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, Set - .of(AWTKeyStroke.getAWTKeyStroke(KeyEvent.VK_UP, 0), AWTKeyStroke + .of(AWTKeyStroke + .getAWTKeyStroke(KeyEvent.VK_UP, 0), AWTKeyStroke .getAWTKeyStroke(KeyEvent.VK_LEFT, 0))); this.setFocusTraversalKeys(KeyboardFocusManager.UP_CYCLE_TRAVERSAL_KEYS, Set .of(AWTKeyStroke.getAWTKeyStroke(KeyEvent.VK_TAB, 0))); this.setFocusTraversalKeys(KeyboardFocusManager.DOWN_CYCLE_TRAVERSAL_KEYS, Set - .of(AWTKeyStroke.getAWTKeyStroke(KeyEvent.VK_TAB, KeyEvent.SHIFT_DOWN_MASK))); + .of(AWTKeyStroke + .getAWTKeyStroke(KeyEvent.VK_TAB, KeyEvent.SHIFT_DOWN_MASK))); } @Override @@ -752,7 +881,8 @@ public class Interface extends JFrame implements SongDisplayListener, ComponentL else { int index = -1; - Component[] children = ((Container) e.getComponent()).getComponents(); + Component[] children = ((Container) e.getComponent()) + .getComponents(); for (int i = 0, l = children.length; index == -1 && i < l; i++) { if (children[i] == e.getOppositeComponent()) diff --git a/interface/src/main/java/edu/regis/universeplayer/player/PlayerControls.java b/interface/src/main/java/edu/regis/universeplayer/player/PlayerControls.java index a0d056b..5637978 100644 --- a/interface/src/main/java/edu/regis/universeplayer/player/PlayerControls.java +++ b/interface/src/main/java/edu/regis/universeplayer/player/PlayerControls.java @@ -8,52 +8,63 @@ import edu.regis.universeplayer.PlaybackInfo; import edu.regis.universeplayer.PlaybackListener; import edu.regis.universeplayer.PlaybackStatus; import edu.regis.universeplayer.Player; +import edu.regis.universeplayer.browserCommands.CommandConfirmation; +import edu.regis.universeplayer.browserCommands.CommandReturn; +import edu.regis.universeplayer.browserCommands.QueryFuture; import edu.regis.universeplayer.data.PlaybackEvent; import edu.regis.universeplayer.data.Queue; import edu.regis.universeplayer.data.Song; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.swing.*; + import java.awt.*; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.LinkedList; +import java.util.Locale; +import java.util.ResourceBundle; import java.util.concurrent.ExecutionException; import java.util.concurrent.ForkJoinPool; /** - * This panel contains the buttons necessary for controlling the playback of audio. + * This panel contains the buttons necessary for controlling the playback of + * audio. * * @author William Hubbard * @version 0.1 */ public class PlayerControls extends JPanel implements Queue.SongChangeListener, PlaybackListener { - private static final Logger logger = LoggerFactory.getLogger(PlayerControls.class); + private static final Logger logger = LoggerFactory + .getLogger(PlayerControls.class); + private static final ResourceBundle langs = ResourceBundle + .getBundle("lang.interface", Locale.getDefault()); private final ImageIcon PLAY_ICON, PAUSE_ICON; - + /** * A reference to the song currently playing. */ private Song currentSong = null; private Player currentPlayer = null; - + private final JButton playButton; private final JButton nextButton; private final JButton prevButton; private final JProgressBar progress; private final JProgressBar updateProgress; - + private final ForkJoinPool service = new ForkJoinPool(); - + /** * A list of all things interested in knowing when we trigger a command. */ private final LinkedList listeners = new LinkedList<>(); - + public PlayerControls() { final Dimension BUTTON_SIZE = new Dimension(32, 32); @@ -62,71 +73,84 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener, JPanel buttonCont, progressCont; FlowLayout buttonLayout; SpringLayout progressLayout; - + SpringLayout layout = new SpringLayout(); this.setLayout(layout); this.setFocusable(true); this.setFocusCycleRoot(false); - + buttonLayout = new FlowLayout(); buttonCont = new JPanel(buttonLayout); this.add(buttonCont); - - this.prevButton = new JButton(Interface.getInstance().actions.get("playback.skipPrev")); + + this.prevButton = new JButton(Interface.getInstance().actions + .get("playback.skipPrev")); this.prevButton.setText(""); icon = new ImageIcon(this.getClass() - .getResource("/gui/icons/skipPrev.png"), "Previous Button"); - icon.setImage(icon.getImage().getScaledInstance(ICON_SIZE.width, ICON_SIZE.height, 0)); + .getResource("/gui/icons/skipPrev.png"), "Previous Button"); + icon.setImage(icon.getImage() + .getScaledInstance(ICON_SIZE.width, ICON_SIZE.height, 0)); this.prevButton.setIcon(icon); this.prevButton.setPreferredSize(BUTTON_SIZE); buttonCont.add(this.prevButton); - - this.playButton = new JButton(Interface.getInstance().actions.get("playback.toggle")); + + this.playButton = new JButton(Interface.getInstance().actions + .get("playback.toggle")); this.playButton.setText(""); - PAUSE_ICON = icon = new ImageIcon(this.getClass().getResource("/gui/icons/pause.png"), "Pause Button"); - icon.setImage(icon.getImage().getScaledInstance(ICON_SIZE.width, ICON_SIZE.height, 0)); - PLAY_ICON = icon = new ImageIcon(this.getClass().getResource("/gui/icons/play.png"), "Play Button"); - icon.setImage(icon.getImage().getScaledInstance(ICON_SIZE.width, ICON_SIZE.height, 0)); + PAUSE_ICON = icon = new ImageIcon(this.getClass() + .getResource("/gui/icons/pause.png"), "Pause Button"); + icon.setImage(icon.getImage() + .getScaledInstance(ICON_SIZE.width, ICON_SIZE.height, 0)); + PLAY_ICON = icon = new ImageIcon(this.getClass() + .getResource("/gui/icons/play.png"), "Play Button"); + icon.setImage(icon.getImage() + .getScaledInstance(ICON_SIZE.width, ICON_SIZE.height, 0)); this.playButton.setIcon(icon); this.playButton.setPreferredSize(BUTTON_SIZE); buttonCont.add(this.playButton); - - this.nextButton = new JButton(Interface.getInstance().actions.get("playback.skipNext")); + + this.nextButton = new JButton(Interface.getInstance().actions + .get("playback.skipNext")); this.nextButton.setText(""); - icon = new ImageIcon(this.getClass().getResource("/gui/icons/skipNext.png"), "Next Button"); - icon.setImage(icon.getImage().getScaledInstance(ICON_SIZE.width, ICON_SIZE.height, 0)); + icon = new ImageIcon(this.getClass() + .getResource("/gui/icons/skipNext.png"), "Next Button"); + icon.setImage(icon.getImage() + .getScaledInstance(ICON_SIZE.width, ICON_SIZE.height, 0)); this.nextButton.setIcon(icon); this.nextButton.setPreferredSize(BUTTON_SIZE); buttonCont.add(this.nextButton); - + progressLayout = new SpringLayout(); progressCont = new JPanel(progressLayout); this.add(progressCont); - + this.progress = new JProgressBar(); this.progress.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { - seek((float) e.getX() / (float) e.getComponent().getWidth() * ((JProgressBar) e.getComponent()).getMaximum()); + seek((float) e.getX() / (float) e.getComponent() + .getWidth() * ((JProgressBar) e + .getComponent()).getMaximum()); logger.debug("Changing time"); } }); this.add(this.progress); - + this.updateProgress = new JProgressBar(); this.updateProgress.setStringPainted(true); this.setUpdateProgress(0, 0, null); this.add(this.updateProgress); - + this.addFocusListener(new FocusAdapter() { @Override public void focusGained(FocusEvent e) { int index = -1; - Component[] children = ((Container) e.getComponent()).getComponents(); + Component[] children = ((Container) e.getComponent()) + .getComponents(); for (int i = 0, l = children.length; index == -1 && i < l; i++) { if (children[i] == e.getOppositeComponent()) @@ -144,7 +168,7 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener, } } }); - + layout.putConstraint(SpringLayout.NORTH, buttonCont, 0, SpringLayout.NORTH, this); layout.putConstraint(SpringLayout.WEST, buttonCont, 0, SpringLayout.WEST, this); layout.putConstraint(SpringLayout.EAST, buttonCont, 0, SpringLayout.EAST, this); @@ -153,20 +177,39 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener, layout.putConstraint(SpringLayout.EAST, this.progress, 5, SpringLayout.EAST, this); layout.putConstraint(SpringLayout.NORTH, this.updateProgress, 5, SpringLayout.SOUTH, this.progress); layout.putConstraint(SpringLayout.WEST, this.updateProgress, 5, SpringLayout.WEST, this); - + layout.putConstraint(SpringLayout.EAST, this, 5, SpringLayout.EAST, this.progress); layout.putConstraint(SpringLayout.EAST, this, 5, SpringLayout.EAST, this.updateProgress); layout.putConstraint(SpringLayout.SOUTH, this, 5, SpringLayout.SOUTH, this.updateProgress); - + Queue.getInstance().addSongChangeListener(this); } - + private void seek(float value) { this.service.execute(() -> { if (this.currentPlayer != null) { - this.currentPlayer.seek(value); + QueryFuture command = this.currentPlayer.seek(value); + try + { + if (!command.getConfirmation().wasSuccessful()) + { + JOptionPane.showMessageDialog(this, + command.getConfirmation().getError(), + command.getConfirmation().getMessage(), + JOptionPane.ERROR_MESSAGE); + logger.error("Could not run command", + command.getConfirmation().getError()); + } + } + catch (ExecutionException | InterruptedException e) + { + logger.error("Could not run command", e); + JOptionPane.showMessageDialog(this, e.getMessage(), langs + .getString( + "error.command"), JOptionPane.ERROR_MESSAGE); + } } }); this.triggerCommandListeners(PlaybackCommand.SEEK, value); @@ -179,28 +222,49 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener, { if (this.currentPlayer != null) { - switch ((PlaybackStatus) this.currentPlayer.getStatus().get()) + QueryFuture status = + this.currentPlayer.getStatus(); + CommandConfirmation confirmation = status.getConfirmation(); + if (status.getConfirmation().wasSuccessful()) { - case PAUSED -> this.currentPlayer.play(); - case STOPPED, EMPTY -> { - if (Queue.getInstance().size() > 0) + switch (status.get()) { - if (Queue.getInstance().getCurrentSong() == null) + case PAUSED -> confirmation = this.currentPlayer.play() + .getConfirmation(); + case STOPPED, EMPTY -> { + if (Queue.getInstance().size() > 0) { - Queue.getInstance().skipToSong(0); - } - else - { - this.currentPlayer.play(); + if (Queue.getInstance() + .getCurrentSong() == null) + { + Queue.getInstance().skipToSong(0); + } + else + { + confirmation = this.currentPlayer.play() + .getConfirmation(); + } } } + } } + if (confirmation != null && !confirmation.wasSuccessful()) + { + JOptionPane.showMessageDialog(this, + confirmation.getError(), + confirmation.getMessage(), + JOptionPane.ERROR_MESSAGE); + logger.error("Could not run command", + confirmation.getError()); } } } catch (ExecutionException | InterruptedException e) { logger.error("Could not get current playback status", e); + JOptionPane.showMessageDialog(this, e.getMessage(), langs + .getString( + "error.command"), JOptionPane.ERROR_MESSAGE); } }); this.triggerCommandListeners(PlaybackCommand.PLAY, null); @@ -213,20 +277,39 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener, { if (this.currentPlayer != null) { - switch ((PlaybackStatus) this.currentPlayer.getStatus().get()) + QueryFuture status = + this.currentPlayer.getStatus(); + CommandConfirmation confirmation = status.getConfirmation(); + if (status.getConfirmation().wasSuccessful()) { - case PLAYING -> this.currentPlayer.pause(); + switch (status.get()) + { + case PLAYING -> confirmation = + this.currentPlayer.pause().getConfirmation(); + } + } + if (confirmation != null && !confirmation.wasSuccessful()) + { + JOptionPane.showMessageDialog(this, + confirmation.getError(), + confirmation.getMessage(), + JOptionPane.ERROR_MESSAGE); + logger.error("Could not run command", + confirmation.getError()); } } } catch (ExecutionException | InterruptedException e) { logger.error("Could not get current playback status", e); + JOptionPane.showMessageDialog(this, e.getMessage(), langs + .getString( + "error.command"), JOptionPane.ERROR_MESSAGE); } }); this.triggerCommandListeners(PlaybackCommand.PAUSE, null); } - + /** * Toggles the playback of the current song. */ @@ -237,34 +320,56 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener, { if (this.currentPlayer != null) { - switch ((PlaybackStatus) this.currentPlayer.getStatus().get()) + QueryFuture status = + this.currentPlayer.getStatus(); + CommandConfirmation confirmation = status.getConfirmation(); + if (status.getConfirmation().wasSuccessful()) { - case PAUSED -> this.currentPlayer.play(); - case PLAYING -> this.currentPlayer.pause(); - case STOPPED, EMPTY -> { - if (Queue.getInstance().size() > 0) + switch (status.get()) { - if (Queue.getInstance().getCurrentSong() == null) + case PAUSED -> confirmation = this.currentPlayer.play() + .getConfirmation(); + case PLAYING -> confirmation = this.currentPlayer.pause() + .getConfirmation(); + case STOPPED, EMPTY -> { + if (Queue.getInstance().size() > 0) { - Queue.getInstance().skipToSong(0); - } - else - { - this.currentPlayer.play(); + if (Queue.getInstance() + .getCurrentSong() == null) + { + Queue.getInstance().skipToSong(0); + } + else + { + confirmation = this.currentPlayer.play() + .getConfirmation(); + } } } + } } + if (confirmation != null && !confirmation.wasSuccessful()) + { + JOptionPane.showMessageDialog(this, + confirmation.getError(), + confirmation.getMessage(), + JOptionPane.ERROR_MESSAGE); + logger.error("Could not run command", + confirmation.getError()); } } } catch (ExecutionException | InterruptedException e) { logger.error("Could not get current playback status", e); + JOptionPane.showMessageDialog(this, e.getMessage(), langs + .getString( + "error.command"), JOptionPane.ERROR_MESSAGE); } }); this.triggerCommandListeners(PlaybackCommand.PLAY, null); } - + /** * Skips to the next song. */ @@ -273,7 +378,7 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener, Queue.getInstance().skipPrev(); this.triggerCommandListeners(PlaybackCommand.PREVIOUS, null); } - + /** * Skips to the next song. */ @@ -282,7 +387,7 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener, Queue.getInstance().skipNext(); this.triggerCommandListeners(PlaybackCommand.NEXT, null); } - + void setUpdateProgress(int updated, int toUpdate, String updating) { this.updateProgress.setString(updating); @@ -307,7 +412,7 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener, } } } - + /** * Adds a listener for playback commands. * @@ -317,7 +422,7 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener, { this.listeners.add(listener); } - + /** * Removes a playback listener. * @@ -327,7 +432,7 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener, { this.listeners.remove(listener); } - + /** * Triggers all the command listeners. * @@ -341,7 +446,7 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener, listener.onCommand(command, data); } } - + @Override public void onSongChange(Queue queue) { @@ -349,14 +454,37 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener, { if (this.currentPlayer != null) { - this.currentPlayer.stopSong(); + this.service.execute(() -> { + QueryFuture command = this.currentPlayer.stopSong(); + try + { + if (!command.getConfirmation().wasSuccessful()) + { + JOptionPane.showMessageDialog(this, + command.getConfirmation().getError(), + command.getConfirmation().getMessage(), + JOptionPane.ERROR_MESSAGE); + logger.error("Could not run command", + command.getConfirmation().getError()); + } + } + catch (ExecutionException | InterruptedException e) + { + logger.error("Could not get current playback status", e); + JOptionPane + .showMessageDialog(this, e.getMessage(), langs + .getString( + "error.command"), JOptionPane.ERROR_MESSAGE); + } + }); } } this.currentSong = queue.getCurrentSong(); this.playButton.setIcon(PLAY_ICON); if (this.currentSong != null) { - this.currentPlayer = Player.REGISTERED_PLAYERS.get(this.currentSong.getClass()); + this.currentPlayer = Player.REGISTERED_PLAYERS + .get(this.currentSong.getClass()); this.progress.setMaximum((int) (this.currentSong.duration / 1000)); if (this.currentPlayer != null) { @@ -364,12 +492,37 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener, { this.currentPlayer.addPlaybackListener(this); } - this.currentPlayer.loadSong(this.currentSong); + this.service.execute(() -> { + QueryFuture command = + this.currentPlayer.loadSong(this.currentSong); + try + { + if (!command.getConfirmation().wasSuccessful()) + { + JOptionPane.showMessageDialog(this, + command.getConfirmation().getError(), + command.getConfirmation().getMessage(), + JOptionPane.ERROR_MESSAGE); + logger.error("Could not run command", + command.getConfirmation().getError()); + } + } + catch (ExecutionException | InterruptedException e) + { + logger.error("Could not get current playback status", e); + JOptionPane + .showMessageDialog(this, e.getMessage(), langs + .getString( + "error.command"), JOptionPane.ERROR_MESSAGE); + } + }); + // this.currentPlayer.play(); } else { - logger.error("No logger found for song {}", this.currentSong.getClass()); + logger.error("No logger found for song {}", this.currentSong + .getClass()); } } else @@ -378,11 +531,12 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener, this.progress.setMaximum(0); } } - + @Override public void onPlaybackChanged(PlaybackEvent status) { - if (status.getSource() != null && status.getSource() == this.currentPlayer) + if (status.getSource() != null && status + .getSource() == this.currentPlayer) { switch (status.getInfo().getStatus()) { @@ -391,7 +545,8 @@ public class PlayerControls extends JPanel implements Queue.SongChangeListener, case PAUSED, STOPPED, EMPTY -> this.playButton.setIcon(PLAY_ICON); } this.progress.setValue((int) status.getInfo().getPlayTime()); - this.progress.setMaximum((int) (status.getInfo().getSong().duration / 1000)); + this.progress.setMaximum((int) (status.getInfo() + .getSong().duration / 1000)); } } } diff --git a/interface/src/main/resources/lang/interface.properties b/interface/src/main/resources/lang/interface.properties index f140e2b..8fa82b8 100644 --- a/interface/src/main/resources/lang/interface.properties +++ b/interface/src/main/resources/lang/interface.properties @@ -14,6 +14,7 @@ menu.view.title=View menu.view.collections=Collections menu.playback.title=Playback menu.help.title=Help +menu.debug.title=Debugging albumInfo.album=Album albumInfo.artists=Artists @@ -42,6 +43,7 @@ actions.refresh=Refresh Song Cache actions.exit=Exit actions.logs=Show Logs +actions.debug.error=Throw Error actions.about=About @@ -61,4 +63,7 @@ actions.playback.skipPrev=Skip Previously actions.playback.skipNext=Skip Next error.generic=Error! -error.browser.launch=Could not open browser background \ No newline at end of file +error.command=Could not run command +error.debug.error=Could not create error +error.browser.launch=Could not open browser background +error.browser.null=Could not find browser background \ No newline at end of file