Fixed UI exceptions caused by modifications to the UI outside the event thread.
This commit is contained in:
parent
acde4ed63f
commit
812bf26b3c
@ -1,5 +1,7 @@
|
|||||||
package com.rarchives.ripme.ripper;
|
package com.rarchives.ripme.ripper;
|
||||||
|
|
||||||
|
import com.rarchives.ripme.ui.MainWindow;
|
||||||
|
import com.rarchives.ripme.ui.RipStatusHandler;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
@ -17,6 +19,7 @@ import org.apache.log4j.Logger;
|
|||||||
import com.rarchives.ripme.ui.RipStatusMessage;
|
import com.rarchives.ripme.ui.RipStatusMessage;
|
||||||
import com.rarchives.ripme.ui.RipStatusMessage.STATUS;
|
import com.rarchives.ripme.ui.RipStatusMessage.STATUS;
|
||||||
import com.rarchives.ripme.utils.Utils;
|
import com.rarchives.ripme.utils.Utils;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
public abstract class AbstractRipper
|
public abstract class AbstractRipper
|
||||||
extends Observable
|
extends Observable
|
||||||
@ -30,11 +33,11 @@ public abstract class AbstractRipper
|
|||||||
protected URL url;
|
protected URL url;
|
||||||
protected File workingDir;
|
protected File workingDir;
|
||||||
protected DownloadThreadPool threadPool;
|
protected DownloadThreadPool threadPool;
|
||||||
protected Observer observer = null;
|
protected RipStatusHandler observer = null;
|
||||||
|
|
||||||
protected Map<URL, File> itemsPending = new HashMap<URL, File>();
|
protected Map<URL, File> itemsPending = Collections.synchronizedMap(new HashMap<URL, File>());
|
||||||
protected Map<URL, File> itemsCompleted = new HashMap<URL, File>();
|
protected Map<URL, File> itemsCompleted = Collections.synchronizedMap(new HashMap<URL, File>());
|
||||||
protected Map<URL, String> itemsErrored = new HashMap<URL, String>();
|
protected Map<URL, String> itemsErrored = Collections.synchronizedMap(new HashMap<URL, String>());
|
||||||
protected boolean completed = true;
|
protected boolean completed = true;
|
||||||
|
|
||||||
public abstract void rip() throws IOException;
|
public abstract void rip() throws IOException;
|
||||||
@ -59,7 +62,7 @@ public abstract class AbstractRipper
|
|||||||
this.threadPool = new DownloadThreadPool();
|
this.threadPool = new DownloadThreadPool();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setObserver(Observer obs) {
|
public void setObserver(RipStatusHandler obs) {
|
||||||
this.observer = obs;
|
this.observer = obs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,7 +165,6 @@ public abstract class AbstractRipper
|
|||||||
public void retrievingSource(URL url) {
|
public void retrievingSource(URL url) {
|
||||||
RipStatusMessage msg = new RipStatusMessage(STATUS.LOADING_RESOURCE, url);
|
RipStatusMessage msg = new RipStatusMessage(STATUS.LOADING_RESOURCE, url);
|
||||||
observer.update(this, msg);
|
observer.update(this, msg);
|
||||||
observer.notifyAll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -179,13 +181,11 @@ public abstract class AbstractRipper
|
|||||||
try {
|
try {
|
||||||
String path = Utils.removeCWD(saveAs);
|
String path = Utils.removeCWD(saveAs);
|
||||||
RipStatusMessage msg = new RipStatusMessage(STATUS.DOWNLOAD_COMPLETE, path);
|
RipStatusMessage msg = new RipStatusMessage(STATUS.DOWNLOAD_COMPLETE, path);
|
||||||
synchronized(observer) {
|
itemsPending.remove(url);
|
||||||
itemsPending.remove(url);
|
itemsCompleted.put(url, saveAs);
|
||||||
itemsCompleted.put(url, saveAs);
|
observer.update(this, msg);
|
||||||
observer.update(this, msg);
|
|
||||||
observer.notifyAll();
|
checkIfComplete();
|
||||||
checkIfComplete();
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Exception while updating observer: ", e);
|
logger.error("Exception while updating observer: ", e);
|
||||||
}
|
}
|
||||||
@ -200,13 +200,11 @@ public abstract class AbstractRipper
|
|||||||
if (observer == null) {
|
if (observer == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
synchronized(observer) {
|
itemsPending.remove(url);
|
||||||
itemsPending.remove(url);
|
itemsErrored.put(url, reason);
|
||||||
itemsErrored.put(url, reason);
|
observer.update(this, new RipStatusMessage(STATUS.DOWNLOAD_ERRORED, url + " : " + reason));
|
||||||
observer.update(this, new RipStatusMessage(STATUS.DOWNLOAD_ERRORED, url + " : " + reason));
|
|
||||||
observer.notifyAll();
|
checkIfComplete();
|
||||||
checkIfComplete();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -219,12 +217,12 @@ public abstract class AbstractRipper
|
|||||||
if (observer == null) {
|
if (observer == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
synchronized(observer) {
|
|
||||||
itemsPending.remove(url);
|
itemsPending.remove(url);
|
||||||
itemsErrored.put(url, message);
|
itemsErrored.put(url, message);
|
||||||
observer.update(this, new RipStatusMessage(STATUS.DOWNLOAD_WARN, url + " : " + message));
|
observer.update(this, new RipStatusMessage(STATUS.DOWNLOAD_WARN, url + " : " + message));
|
||||||
observer.notifyAll();
|
|
||||||
}
|
|
||||||
checkIfComplete();
|
checkIfComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,16 +233,13 @@ public abstract class AbstractRipper
|
|||||||
if (observer == null) {
|
if (observer == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
synchronized (observer) {
|
|
||||||
if (!completed && itemsPending.size() == 0) {
|
if (!completed && itemsPending.isEmpty()) {
|
||||||
completed = true;
|
completed = true;
|
||||||
logger.info(" Rip completed!");
|
logger.info(" Rip completed!");
|
||||||
observer.update(this,
|
|
||||||
new RipStatusMessage(
|
RipStatusMessage msg = new RipStatusMessage(STATUS.RIP_COMPLETE, workingDir);
|
||||||
STATUS.RIP_COMPLETE,
|
observer.update(this, msg);
|
||||||
workingDir));
|
|
||||||
observer.notifyAll();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -325,10 +320,7 @@ public abstract class AbstractRipper
|
|||||||
if (observer == null) {
|
if (observer == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
synchronized (observer) {
|
observer.update(this, new RipStatusMessage(status, message));
|
||||||
observer.update(this, new RipStatusMessage(status, message));
|
|
||||||
observer.notifyAll();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -46,7 +46,7 @@ import com.rarchives.ripme.utils.Utils;
|
|||||||
/**
|
/**
|
||||||
* Everything UI-related starts and ends here.
|
* Everything UI-related starts and ends here.
|
||||||
*/
|
*/
|
||||||
public class MainWindow implements Runnable {
|
public class MainWindow implements Runnable, RipStatusHandler {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(MainWindow.class);
|
private static final Logger logger = Logger.getLogger(MainWindow.class);
|
||||||
|
|
||||||
@ -101,7 +101,7 @@ public class MainWindow implements Runnable {
|
|||||||
mainFrame.setVisible(true);
|
mainFrame.setVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void status(String text) {
|
public synchronized static void status(String text) {
|
||||||
statusLabel.setText(text);
|
statusLabel.setText(text);
|
||||||
mainFrame.pack();
|
mainFrame.pack();
|
||||||
}
|
}
|
||||||
@ -276,24 +276,14 @@ public class MainWindow implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void appendLog(final String text, final Color color) {
|
private void appendLog(final String text, final Color color) {
|
||||||
try {
|
SimpleAttributeSet sas = new SimpleAttributeSet();
|
||||||
SwingUtilities.invokeAndWait(new Runnable() {
|
StyleConstants.setForeground(sas, color);
|
||||||
@Override
|
StyledDocument sd = logText.getStyledDocument();
|
||||||
public void run() {
|
try {
|
||||||
SimpleAttributeSet sas = new SimpleAttributeSet();
|
sd.insertString(sd.getLength(), text + "\n", sas);
|
||||||
StyleConstants.setForeground(sas, color);
|
} catch (BadLocationException e) { }
|
||||||
StyledDocument sd = logText.getStyledDocument();
|
|
||||||
try {
|
logText.setCaretPosition(sd.getLength());
|
||||||
sd.insertString(sd.getLength(), text + "\n", sas);
|
|
||||||
} catch (BadLocationException e) { }
|
|
||||||
logText.setCaretPosition(logText.getText().length());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} catch (InvocationTargetException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadHistory() {
|
private void loadHistory() {
|
||||||
@ -359,7 +349,7 @@ public class MainWindow implements Runnable {
|
|||||||
try {
|
try {
|
||||||
AbstractRipper ripper = AbstractRipper.getRipper(url);
|
AbstractRipper ripper = AbstractRipper.getRipper(url);
|
||||||
ripTextfield.setText(ripper.getURL().toExternalForm());
|
ripTextfield.setText(ripper.getURL().toExternalForm());
|
||||||
ripper.setObserver(new RipStatusHandler());
|
ripper.setObserver((RipStatusHandler) this);
|
||||||
Thread t = new Thread(ripper);
|
Thread t = new Thread(ripper);
|
||||||
t.start();
|
t.start();
|
||||||
return t;
|
return t;
|
||||||
@ -376,63 +366,82 @@ public class MainWindow implements Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RipStatusHandler implements Observer {
|
private class StatusEvent implements Runnable {
|
||||||
public void update(Observable observable, Object object) {
|
private final AbstractRipper ripper;
|
||||||
RipStatusMessage msg = (RipStatusMessage) object;
|
private final RipStatusMessage msg;
|
||||||
|
|
||||||
int completedPercent = ((AbstractRipper) observable).getCompletionPercentage();
|
public StatusEvent(AbstractRipper ripper, RipStatusMessage msg) {
|
||||||
statusProgress.setValue(completedPercent);
|
this.ripper = ripper;
|
||||||
status( ((AbstractRipper)observable).getStatusText() );
|
this.msg = msg;
|
||||||
|
|
||||||
switch(msg.getStatus()) {
|
|
||||||
case LOADING_RESOURCE:
|
|
||||||
case DOWNLOAD_STARTED:
|
|
||||||
appendLog( "Downloading: " + (String) msg.getObject(), Color.BLACK);
|
|
||||||
break;
|
|
||||||
case DOWNLOAD_COMPLETE:
|
|
||||||
appendLog( "Completed: " + (String) msg.getObject(), Color.GREEN);
|
|
||||||
break;
|
|
||||||
case DOWNLOAD_ERRORED:
|
|
||||||
appendLog( "Error: " + (String) msg.getObject(), Color.RED);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case DOWNLOAD_WARN:
|
|
||||||
appendLog( "Warn: " + (String) msg.getObject(), Color.ORANGE);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case RIP_COMPLETE:
|
|
||||||
if (!historyListModel.contains(ripTextfield.getText())) {
|
|
||||||
historyListModel.addElement(ripTextfield.getText());
|
|
||||||
}
|
|
||||||
saveHistory();
|
|
||||||
ripButton.setEnabled(true);
|
|
||||||
ripTextfield.setEnabled(true);
|
|
||||||
statusProgress.setValue(100);
|
|
||||||
statusLabel.setVisible(false);
|
|
||||||
openButton.setVisible(true);
|
|
||||||
File f = (File) msg.getObject();
|
|
||||||
String prettyFile = Utils.removeCWD(f);
|
|
||||||
openButton.setText("Open " + prettyFile);
|
|
||||||
appendLog( "Rip complete, saved to " + prettyFile, Color.GREEN);
|
|
||||||
openButton.setActionCommand(f.toString());
|
|
||||||
openButton.addActionListener(new ActionListener() {
|
|
||||||
@Override
|
|
||||||
public void actionPerformed(ActionEvent event) {
|
|
||||||
try {
|
|
||||||
Desktop.getDesktop().open(new File(event.getActionCommand()));
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
mainFrame.pack();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
handleEvent(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleEvent(StatusEvent evt) {
|
||||||
|
RipStatusMessage msg = evt.msg;
|
||||||
|
|
||||||
|
int completedPercent = evt.ripper.getCompletionPercentage();
|
||||||
|
statusProgress.setValue(completedPercent);
|
||||||
|
status( evt.ripper.getStatusText() );
|
||||||
|
|
||||||
|
switch(msg.getStatus()) {
|
||||||
|
case LOADING_RESOURCE:
|
||||||
|
case DOWNLOAD_STARTED:
|
||||||
|
appendLog( "Downloading: " + (String) msg.getObject(), Color.BLACK);
|
||||||
|
break;
|
||||||
|
case DOWNLOAD_COMPLETE:
|
||||||
|
appendLog( "Completed: " + (String) msg.getObject(), Color.GREEN);
|
||||||
|
break;
|
||||||
|
case DOWNLOAD_ERRORED:
|
||||||
|
appendLog( "Error: " + (String) msg.getObject(), Color.RED);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DOWNLOAD_WARN:
|
||||||
|
appendLog( "Warn: " + (String) msg.getObject(), Color.ORANGE);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RIP_COMPLETE:
|
||||||
|
if (!historyListModel.contains(ripTextfield.getText())) {
|
||||||
|
historyListModel.addElement(ripTextfield.getText());
|
||||||
|
}
|
||||||
|
saveHistory();
|
||||||
|
ripButton.setEnabled(true);
|
||||||
|
ripTextfield.setEnabled(true);
|
||||||
|
statusProgress.setValue(100);
|
||||||
|
statusLabel.setVisible(false);
|
||||||
|
openButton.setVisible(true);
|
||||||
|
File f = (File) msg.getObject();
|
||||||
|
String prettyFile = Utils.removeCWD(f);
|
||||||
|
openButton.setText("Open " + prettyFile);
|
||||||
|
appendLog( "Rip complete, saved to " + prettyFile, Color.GREEN);
|
||||||
|
openButton.setActionCommand(f.toString());
|
||||||
|
openButton.addActionListener(new ActionListener() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent event) {
|
||||||
|
try {
|
||||||
|
Desktop.getDesktop().open(new File(event.getActionCommand()));
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mainFrame.pack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update(AbstractRipper ripper, RipStatusMessage message) {
|
||||||
|
StatusEvent event = new StatusEvent(ripper, message);
|
||||||
|
SwingUtilities.invokeLater(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Simple TextPane that allows horizontal scrolling. */
|
/** Simple TextPane that allows horizontal scrolling. */
|
||||||
class JTextPaneNoWrap extends JTextPane {
|
class JTextPaneNoWrap extends JTextPane {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@Override
|
||||||
public boolean getScrollableTracksViewportWidth() {
|
public boolean getScrollableTracksViewportWidth() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
14
src/main/java/com/rarchives/ripme/ui/RipStatusHandler.java
Normal file
14
src/main/java/com/rarchives/ripme/ui/RipStatusHandler.java
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
package com.rarchives.ripme.ui;
|
||||||
|
|
||||||
|
import com.rarchives.ripme.ripper.AbstractRipper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Mads
|
||||||
|
*/
|
||||||
|
public interface RipStatusHandler {
|
||||||
|
|
||||||
|
public void update(AbstractRipper ripper, RipStatusMessage message);
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user