From 07c73701db6390e92384a707c3b4363ee45d8e18 Mon Sep 17 00:00:00 2001 From: 4pr0n Date: Sat, 1 Mar 2014 02:13:32 -0800 Subject: [PATCH] Added basic GUI with event handling. Long way to go, but it's a start. --- src/main/java/com/rarchives/ripme/App.java | 22 +++-- .../ripme/ripper/AbstractRipper.java | 78 +++++++++++++++- .../ripme/ripper/DownloadFileThread.java | 12 ++- .../ripme/ripper/rippers/ImgurRipper.java | 3 +- .../com/rarchives/ripme/ui/MainWindow.java | 91 +++++++++++++++++++ .../rarchives/ripme/ui/RipStatusMessage.java | 37 ++++++++ 6 files changed, 230 insertions(+), 13 deletions(-) create mode 100644 src/main/java/com/rarchives/ripme/ui/MainWindow.java create mode 100644 src/main/java/com/rarchives/ripme/ui/RipStatusMessage.java diff --git a/src/main/java/com/rarchives/ripme/App.java b/src/main/java/com/rarchives/ripme/App.java index 010280c1..14f6f08f 100644 --- a/src/main/java/com/rarchives/ripme/App.java +++ b/src/main/java/com/rarchives/ripme/App.java @@ -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); } } diff --git a/src/main/java/com/rarchives/ripme/ripper/AbstractRipper.java b/src/main/java/com/rarchives/ripme/ripper/AbstractRipper.java index 336f6256..0d7f4b0e 100644 --- a/src/main/java/com/rarchives/ripme/ripper/AbstractRipper.java +++ b/src/main/java/com/rarchives/ripme/ripper/AbstractRipper.java @@ -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 itemsPending = new HashMap(); + protected Map itemsCompleted = new HashMap(); + protected Map itemsErrored = new HashMap(); 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(); + } + } + } \ No newline at end of file diff --git a/src/main/java/com/rarchives/ripme/ripper/DownloadFileThread.java b/src/main/java/com/rarchives/ripme/ripper/DownloadFileThread.java index 8ab3654f..40babe4e 100644 --- a/src/main/java/com/rarchives/ripme/ripper/DownloadFileThread.java +++ b/src/main/java/com/rarchives/ripme/ripper/DownloadFileThread.java @@ -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); } diff --git a/src/main/java/com/rarchives/ripme/ripper/rippers/ImgurRipper.java b/src/main/java/com/rarchives/ripme/ripper/rippers/ImgurRipper.java index 65ebd7a4..1868026d 100644 --- a/src/main/java/com/rarchives/ripme/ripper/rippers/ImgurRipper.java +++ b/src/main/java/com/rarchives/ripme/ripper/rippers/ImgurRipper.java @@ -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 diff --git a/src/main/java/com/rarchives/ripme/ui/MainWindow.java b/src/main/java/com/rarchives/ripme/ui/MainWindow.java new file mode 100644 index 00000000..1088e656 --- /dev/null +++ b/src/main/java/com/rarchives/ripme/ui/MainWindow.java @@ -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); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/rarchives/ripme/ui/RipStatusMessage.java b/src/main/java/com/rarchives/ripme/ui/RipStatusMessage.java new file mode 100644 index 00000000..3158b6eb --- /dev/null +++ b/src/main/java/com/rarchives/ripme/ui/RipStatusMessage.java @@ -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(); + } +}