Added basic GUI with event handling.
Long way to go, but it's a start.
This commit is contained in:
parent
04ce12b546
commit
07c73701db
@ -3,6 +3,8 @@ package com.rarchives.ripme;
|
|||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
|
||||||
import org.apache.commons.cli.BasicParser;
|
import org.apache.commons.cli.BasicParser;
|
||||||
import org.apache.commons.cli.CommandLine;
|
import org.apache.commons.cli.CommandLine;
|
||||||
import org.apache.commons.cli.HelpFormatter;
|
import org.apache.commons.cli.HelpFormatter;
|
||||||
@ -11,6 +13,7 @@ import org.apache.commons.cli.ParseException;
|
|||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
import com.rarchives.ripme.ripper.AbstractRipper;
|
import com.rarchives.ripme.ripper.AbstractRipper;
|
||||||
|
import com.rarchives.ripme.ui.MainWindow;
|
||||||
import com.rarchives.ripme.utils.Utils;
|
import com.rarchives.ripme.utils.Utils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -23,14 +26,19 @@ public class App {
|
|||||||
public static void main(String[] args) throws MalformedURLException {
|
public static void main(String[] args) throws MalformedURLException {
|
||||||
logger.debug("Initialized");
|
logger.debug("Initialized");
|
||||||
|
|
||||||
CommandLine cl = handleArguments(args);
|
if (args.length > 0) {
|
||||||
|
CommandLine cl = handleArguments(args);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
URL url = new URL(cl.getOptionValue('u'));
|
URL url = new URL(cl.getOptionValue('u'));
|
||||||
rip(url);
|
rip(url);
|
||||||
} catch (MalformedURLException e) {
|
} catch (MalformedURLException e) {
|
||||||
logger.error("[!] Given URL is not valid. Expected URL format is http://domain.com/...");
|
logger.error("[!] Given URL is not valid. Expected URL format is http://domain.com/...");
|
||||||
System.exit(-1);
|
System.exit(-1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
MainWindow mw = new MainWindow();
|
||||||
|
SwingUtilities.invokeLater(mw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,21 +4,35 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
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 org.apache.log4j.Logger;
|
||||||
|
|
||||||
import com.rarchives.ripme.ripper.rippers.ImagearnRipper;
|
import com.rarchives.ripme.ripper.rippers.ImagearnRipper;
|
||||||
import com.rarchives.ripme.ripper.rippers.ImagefapRipper;
|
import com.rarchives.ripme.ripper.rippers.ImagefapRipper;
|
||||||
import com.rarchives.ripme.ripper.rippers.ImgurRipper;
|
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;
|
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);
|
private static final Logger logger = Logger.getLogger(AbstractRipper.class);
|
||||||
|
|
||||||
protected URL url;
|
protected URL url;
|
||||||
protected File workingDir;
|
protected File workingDir;
|
||||||
protected DownloadThreadPool threadPool;
|
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 void rip() throws IOException;
|
||||||
public abstract String getHost();
|
public abstract String getHost();
|
||||||
@ -42,6 +56,10 @@ public abstract class AbstractRipper implements RipperInterface {
|
|||||||
this.threadPool = new DownloadThreadPool();
|
this.threadPool = new DownloadThreadPool();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setObserver(Observer obs) {
|
||||||
|
this.observer = obs;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Queues image to be downloaded and saved.
|
* Queues image to be downloaded and saved.
|
||||||
* Uses filename from URL to decide filename.
|
* Uses filename from URL to decide filename.
|
||||||
@ -74,7 +92,13 @@ public abstract class AbstractRipper implements RipperInterface {
|
|||||||
* Path of the local file to save the content to.
|
* Path of the local file to save the content to.
|
||||||
*/
|
*/
|
||||||
public void addURLToDownload(URL url, File saveAs) {
|
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) {
|
public void addURLToDownload(URL url, String prefix, String subdirectory) {
|
||||||
@ -106,6 +130,46 @@ public abstract class AbstractRipper implements RipperInterface {
|
|||||||
addURLToDownload(url, saveFileAs);
|
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() {
|
public URL getURL() {
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
@ -149,4 +213,12 @@ public abstract class AbstractRipper implements RipperInterface {
|
|||||||
} catch (IOException e) { }
|
} catch (IOException e) { }
|
||||||
throw new Exception("No compatible ripper found");
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -9,6 +9,7 @@ import org.apache.log4j.Logger;
|
|||||||
import org.jsoup.Connection.Response;
|
import org.jsoup.Connection.Response;
|
||||||
import org.jsoup.Jsoup;
|
import org.jsoup.Jsoup;
|
||||||
|
|
||||||
|
import com.rarchives.ripme.ui.RipStatusMessage.STATUS;
|
||||||
import com.rarchives.ripme.utils.Utils;
|
import com.rarchives.ripme.utils.Utils;
|
||||||
|
|
||||||
public class DownloadFileThread extends Thread {
|
public class DownloadFileThread extends Thread {
|
||||||
@ -18,13 +19,15 @@ public class DownloadFileThread extends Thread {
|
|||||||
private URL url;
|
private URL url;
|
||||||
private File saveAs;
|
private File saveAs;
|
||||||
private String prettySaveAs;
|
private String prettySaveAs;
|
||||||
|
private AbstractRipper observer;
|
||||||
private int retries;
|
private int retries;
|
||||||
|
|
||||||
public DownloadFileThread(URL url, File saveAs) {
|
public DownloadFileThread(URL url, File saveAs, AbstractRipper observer) {
|
||||||
super();
|
super();
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.saveAs = saveAs;
|
this.saveAs = saveAs;
|
||||||
this.prettySaveAs = Utils.removeCWD(saveAs);
|
this.prettySaveAs = Utils.removeCWD(saveAs);
|
||||||
|
this.observer = observer;
|
||||||
this.retries = Utils.getConfigInteger("download.retries", 1);
|
this.retries = Utils.getConfigInteger("download.retries", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,10 +35,11 @@ public class DownloadFileThread extends Thread {
|
|||||||
// Check if file already exists
|
// Check if file already exists
|
||||||
if (saveAs.exists()) {
|
if (saveAs.exists()) {
|
||||||
if (Utils.getConfigBoolean("file.overwrite", false)) {
|
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();
|
saveAs.delete();
|
||||||
} else {
|
} else {
|
||||||
logger.info("[!] Skipping " + url + " -- file already exists: " + prettySaveAs);
|
logger.info("[!] Skipping " + url + " -- file already exists: " + prettySaveAs);
|
||||||
|
observer.downloadErrored(url, "File already exists: " + prettySaveAs);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -44,6 +48,7 @@ public class DownloadFileThread extends Thread {
|
|||||||
do {
|
do {
|
||||||
try {
|
try {
|
||||||
logger.info(" Downloading file: " + url + (tries > 0 ? " Retry #" + tries : ""));
|
logger.info(" Downloading file: " + url + (tries > 0 ? " Retry #" + tries : ""));
|
||||||
|
observer.sendUpdate(STATUS.DOWNLOAD_STARTED, url.toExternalForm());
|
||||||
tries += 1;
|
tries += 1;
|
||||||
Response response;
|
Response response;
|
||||||
response = Jsoup.connect(url.toExternalForm())
|
response = Jsoup.connect(url.toExternalForm())
|
||||||
@ -52,15 +57,18 @@ public class DownloadFileThread extends Thread {
|
|||||||
FileOutputStream out = (new FileOutputStream(saveAs));
|
FileOutputStream out = (new FileOutputStream(saveAs));
|
||||||
out.write(response.bodyAsBytes());
|
out.write(response.bodyAsBytes());
|
||||||
out.close();
|
out.close();
|
||||||
|
observer.downloadCompleted(url, saveAs.getCanonicalFile());
|
||||||
break; // Download successful: break out of infinite loop
|
break; // Download successful: break out of infinite loop
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("[!] Exception while downloading file: " + url + " - " + e.getMessage());
|
logger.error("[!] Exception while downloading file: " + url + " - " + e.getMessage());
|
||||||
}
|
}
|
||||||
if (tries > this.retries) {
|
if (tries > this.retries) {
|
||||||
logger.error("[!] Exceeded maximum retries (" + this.retries + ") for URL " + url);
|
logger.error("[!] Exceeded maximum retries (" + this.retries + ") for URL " + url);
|
||||||
|
observer.downloadErrored(url, "Failed to download " + url.toExternalForm());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} while (true);
|
} while (true);
|
||||||
|
observer.downloadCompleted(url, saveAs);
|
||||||
logger.info("[+] Saved " + url + " as " + this.prettySaveAs);
|
logger.info("[+] Saved " + url + " as " + this.prettySaveAs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ import org.jsoup.nodes.Document;
|
|||||||
import org.jsoup.nodes.Element;
|
import org.jsoup.nodes.Element;
|
||||||
|
|
||||||
import com.rarchives.ripme.ripper.AbstractRipper;
|
import com.rarchives.ripme.ripper.AbstractRipper;
|
||||||
|
import com.rarchives.ripme.ui.RipStatusMessage.STATUS;
|
||||||
|
|
||||||
public class ImgurRipper extends AbstractRipper {
|
public class ImgurRipper extends AbstractRipper {
|
||||||
|
|
||||||
@ -67,7 +68,6 @@ public class ImgurRipper extends AbstractRipper {
|
|||||||
public void rip() throws IOException {
|
public void rip() throws IOException {
|
||||||
switch (albumType) {
|
switch (albumType) {
|
||||||
case ALBUM:
|
case ALBUM:
|
||||||
this.url = new URL(this.url.toExternalForm());
|
|
||||||
// Fall-through
|
// Fall-through
|
||||||
case USER_ALBUM:
|
case USER_ALBUM:
|
||||||
ripAlbum(this.url);
|
ripAlbum(this.url);
|
||||||
@ -92,6 +92,7 @@ public class ImgurRipper extends AbstractRipper {
|
|||||||
private void ripAlbum(URL url, String subdirectory) throws IOException {
|
private void ripAlbum(URL url, String subdirectory) throws IOException {
|
||||||
int index = 0;
|
int index = 0;
|
||||||
logger.info(" Retrieving " + url.toExternalForm());
|
logger.info(" Retrieving " + url.toExternalForm());
|
||||||
|
this.sendUpdate(STATUS.LOADING_RESOURCE, url.toExternalForm());
|
||||||
Document doc = Jsoup.connect(url.toExternalForm()).get();
|
Document doc = Jsoup.connect(url.toExternalForm()).get();
|
||||||
|
|
||||||
// Try to use embedded JSON to retrieve images
|
// Try to use embedded JSON to retrieve images
|
||||||
|
91
src/main/java/com/rarchives/ripme/ui/MainWindow.java
Normal file
91
src/main/java/com/rarchives/ripme/ui/MainWindow.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
src/main/java/com/rarchives/ripme/ui/RipStatusMessage.java
Normal file
37
src/main/java/com/rarchives/ripme/ui/RipStatusMessage.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user