Added basic GUI with event handling.

Long way to go, but it's a start.
This commit is contained in:
4pr0n 2014-03-01 02:13:32 -08:00
parent 04ce12b546
commit 07c73701db
6 changed files with 230 additions and 13 deletions

View File

@ -3,6 +3,8 @@ package com.rarchives.ripme;
import java.net.MalformedURLException;
import java.net.URL;
import javax.swing.SwingUtilities;
import org.apache.commons.cli.BasicParser;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.HelpFormatter;
@ -11,6 +13,7 @@ import org.apache.commons.cli.ParseException;
import org.apache.log4j.Logger;
import com.rarchives.ripme.ripper.AbstractRipper;
import com.rarchives.ripme.ui.MainWindow;
import com.rarchives.ripme.utils.Utils;
/**
@ -23,14 +26,19 @@ public class App {
public static void main(String[] args) throws MalformedURLException {
logger.debug("Initialized");
CommandLine cl = handleArguments(args);
if (args.length > 0) {
CommandLine cl = handleArguments(args);
try {
URL url = new URL(cl.getOptionValue('u'));
rip(url);
} catch (MalformedURLException e) {
logger.error("[!] Given URL is not valid. Expected URL format is http://domain.com/...");
System.exit(-1);
try {
URL url = new URL(cl.getOptionValue('u'));
rip(url);
} catch (MalformedURLException e) {
logger.error("[!] Given URL is not valid. Expected URL format is http://domain.com/...");
System.exit(-1);
}
} else {
MainWindow mw = new MainWindow();
SwingUtilities.invokeLater(mw);
}
}

View File

@ -4,21 +4,35 @@ import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import org.apache.log4j.Logger;
import com.rarchives.ripme.ripper.rippers.ImagearnRipper;
import com.rarchives.ripme.ripper.rippers.ImagefapRipper;
import com.rarchives.ripme.ripper.rippers.ImgurRipper;
import com.rarchives.ripme.ui.RipStatusMessage;
import com.rarchives.ripme.ui.RipStatusMessage.STATUS;
import com.rarchives.ripme.utils.Utils;
public abstract class AbstractRipper implements RipperInterface {
public abstract class AbstractRipper
extends Observable
implements RipperInterface {
private static final Logger logger = Logger.getLogger(AbstractRipper.class);
protected URL url;
protected File workingDir;
protected DownloadThreadPool threadPool;
protected Observer observer = null;
protected int itemsTotal;
protected Map<URL, File> itemsPending = new HashMap<URL, File>();
protected Map<URL, File> itemsCompleted = new HashMap<URL, File>();
protected Map<URL, String> itemsErrored = new HashMap<URL, String>();
public abstract void rip() throws IOException;
public abstract String getHost();
@ -41,6 +55,10 @@ public abstract class AbstractRipper implements RipperInterface {
setWorkingDir(url);
this.threadPool = new DownloadThreadPool();
}
public void setObserver(Observer obs) {
this.observer = obs;
}
/**
* Queues image to be downloaded and saved.
@ -74,7 +92,13 @@ public abstract class AbstractRipper implements RipperInterface {
* Path of the local file to save the content to.
*/
public void addURLToDownload(URL url, File saveAs) {
threadPool.addThread(new DownloadFileThread(url, saveAs));
if (itemsPending.containsKey(url) || itemsCompleted.containsKey(url)) {
// Item is already downloaded/downloading, skip it.
logger.info("Skipping duplicate URL: " + url);
return;
}
itemsPending.put(url, saveAs);
threadPool.addThread(new DownloadFileThread(url, saveAs, this));
}
public void addURLToDownload(URL url, String prefix, String subdirectory) {
@ -106,6 +130,46 @@ public abstract class AbstractRipper implements RipperInterface {
addURLToDownload(url, saveFileAs);
}
public void retrievingSource(URL url) {
RipStatusMessage msg = new RipStatusMessage(STATUS.LOADING_RESOURCE, url);
observer.update(this, msg);
observer.notifyAll();
}
public void downloadCompleted(URL url, File saveAs) {
try {
String path = saveAs.getCanonicalPath();
RipStatusMessage msg = new RipStatusMessage(STATUS.DOWNLOAD_COMPLETE, path);
synchronized(observer) {
itemsPending.remove(url);
itemsCompleted.put(url, saveAs);
observer.update(this, msg);
observer.notifyAll();
checkIfComplete();
}
} catch (Exception e) {
logger.error("Exception while updating observer: ", e);
}
}
public void downloadErrored(URL url, String reason) {
synchronized(observer) {
itemsPending.remove(url);
itemsErrored.put(url, reason);
observer.update(this, new RipStatusMessage(STATUS.DOWNLOAD_ERRORED, url + " : " + reason));
observer.notifyAll();
checkIfComplete();
}
}
private void checkIfComplete() {
if (itemsPending.size() == 0) {
logger.info("Rip completed!");
observer.update(this, new RipStatusMessage(STATUS.RIP_COMPLETE, workingDir));
observer.notifyAll();
}
}
public URL getURL() {
return url;
}
@ -123,7 +187,7 @@ public abstract class AbstractRipper implements RipperInterface {
}
logger.debug("Set working directory to: " + this.workingDir);
}
/**
* Finds, instantiates, and returns a compatible ripper for given URL.
* @param url
@ -149,4 +213,12 @@ public abstract class AbstractRipper implements RipperInterface {
} catch (IOException e) { }
throw new Exception("No compatible ripper found");
}
public void sendUpdate(STATUS status, Object message) {
synchronized (observer) {
observer.update(this, new RipStatusMessage(status, message));
observer.notifyAll();
}
}
}

View File

@ -9,6 +9,7 @@ import org.apache.log4j.Logger;
import org.jsoup.Connection.Response;
import org.jsoup.Jsoup;
import com.rarchives.ripme.ui.RipStatusMessage.STATUS;
import com.rarchives.ripme.utils.Utils;
public class DownloadFileThread extends Thread {
@ -18,13 +19,15 @@ public class DownloadFileThread extends Thread {
private URL url;
private File saveAs;
private String prettySaveAs;
private AbstractRipper observer;
private int retries;
public DownloadFileThread(URL url, File saveAs) {
public DownloadFileThread(URL url, File saveAs, AbstractRipper observer) {
super();
this.url = url;
this.saveAs = saveAs;
this.prettySaveAs = Utils.removeCWD(saveAs);
this.observer = observer;
this.retries = Utils.getConfigInteger("download.retries", 1);
}
@ -32,10 +35,11 @@ public class DownloadFileThread extends Thread {
// Check if file already exists
if (saveAs.exists()) {
if (Utils.getConfigBoolean("file.overwrite", false)) {
logger.info("[!] File already exists and 'file.overwrite' is true, deleting: " + prettySaveAs);
logger.info("[!] Deleting existing file" + prettySaveAs);
saveAs.delete();
} else {
logger.info("[!] Skipping " + url + " -- file already exists: " + prettySaveAs);
observer.downloadErrored(url, "File already exists: " + prettySaveAs);
return;
}
}
@ -44,6 +48,7 @@ public class DownloadFileThread extends Thread {
do {
try {
logger.info(" Downloading file: " + url + (tries > 0 ? " Retry #" + tries : ""));
observer.sendUpdate(STATUS.DOWNLOAD_STARTED, url.toExternalForm());
tries += 1;
Response response;
response = Jsoup.connect(url.toExternalForm())
@ -52,15 +57,18 @@ public class DownloadFileThread extends Thread {
FileOutputStream out = (new FileOutputStream(saveAs));
out.write(response.bodyAsBytes());
out.close();
observer.downloadCompleted(url, saveAs.getCanonicalFile());
break; // Download successful: break out of infinite loop
} catch (IOException e) {
logger.error("[!] Exception while downloading file: " + url + " - " + e.getMessage());
}
if (tries > this.retries) {
logger.error("[!] Exceeded maximum retries (" + this.retries + ") for URL " + url);
observer.downloadErrored(url, "Failed to download " + url.toExternalForm());
return;
}
} while (true);
observer.downloadCompleted(url, saveAs);
logger.info("[+] Saved " + url + " as " + this.prettySaveAs);
}

View File

@ -15,6 +15,7 @@ import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import com.rarchives.ripme.ripper.AbstractRipper;
import com.rarchives.ripme.ui.RipStatusMessage.STATUS;
public class ImgurRipper extends AbstractRipper {
@ -67,7 +68,6 @@ public class ImgurRipper extends AbstractRipper {
public void rip() throws IOException {
switch (albumType) {
case ALBUM:
this.url = new URL(this.url.toExternalForm());
// Fall-through
case USER_ALBUM:
ripAlbum(this.url);
@ -92,6 +92,7 @@ public class ImgurRipper extends AbstractRipper {
private void ripAlbum(URL url, String subdirectory) throws IOException {
int index = 0;
logger.info(" Retrieving " + url.toExternalForm());
this.sendUpdate(STATUS.LOADING_RESOURCE, url.toExternalForm());
Document doc = Jsoup.connect(url.toExternalForm()).get();
// Try to use embedded JSON to retrieve images

View File

@ -0,0 +1,91 @@
package com.rarchives.ripme.ui;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.net.URL;
import java.util.Observable;
import java.util.Observer;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;
import com.rarchives.ripme.ripper.AbstractRipper;
public class MainWindow implements Runnable {
private static final String WINDOW_TITLE = "RipMe";
private static JFrame mainFrame;
private static JTextField ripTextfield;
private static JButton ripButton;
private static JLabel ripStatus;
public MainWindow() {
createUI();
setupHandlers();
}
private void createUI() {
mainFrame = new JFrame(WINDOW_TITLE);
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
ripTextfield = new JTextField("", 20);
ripButton = new JButton("rip");
ripStatus = new JLabel("inactive");
mainFrame.getContentPane().add(ripTextfield, BorderLayout.WEST);
mainFrame.getContentPane().add(ripButton, BorderLayout.EAST);
mainFrame.getContentPane().add(ripStatus, BorderLayout.SOUTH);
}
private void setupHandlers() {
ripButton.addActionListener(new RipButtonHandler());
}
public void run() {
mainFrame.pack();
mainFrame.setLocationRelativeTo(null);
mainFrame.setVisible(true);
}
public static void status(String text) {
ripStatus.setText(text);
}
class RipButtonHandler implements ActionListener {
public void actionPerformed(ActionEvent event) {
try {
URL url = new URL(ripTextfield.getText());
AbstractRipper ripper = AbstractRipper.getRipper(url);
ripper.setObserver(new RipStatusHandler());
ripper.rip();
} catch (Exception e) {
status("Error: " + e.getMessage());
return;
}
}
}
class RipStatusHandler implements Observer {
public void update(Observable observable, Object object) {
RipStatusMessage msg = (RipStatusMessage) object;
System.err.println("Observer update, object: " + object.toString());
switch(msg.getStatus()) {
case LOADING_RESOURCE:
case DOWNLOAD_STARTED:
case DOWNLOAD_COMPLETE:
case DOWNLOAD_ERRORED:
status((String) msg.getObject());
break;
case RIP_COMPLETE:
File f = (File) msg.getObject();
status("RIP COMPLETE: " + f);
}
}
}
}

View File

@ -0,0 +1,37 @@
package com.rarchives.ripme.ui;
public class RipStatusMessage {
public enum STATUS {
LOADING_RESOURCE("Loading Resource"),
DOWNLOAD_STARTED("Download Started"),
DOWNLOAD_COMPLETE("Download Domplete"),
DOWNLOAD_ERRORED("Download Errored"),
RIP_COMPLETE("Rip Complete");
String value;
STATUS(String value) {
this.value = value;
}
}
private STATUS status;
private Object object;
public RipStatusMessage(STATUS status, Object object) {
this.status = status;
this.object = object;
}
public STATUS getStatus() {
return status;
}
public Object getObject() {
return object;
}
public String toString() {
return status.value + ": " + object.toString();
}
}