Single-thread swing works, great start on uniform downloading, still a problem illustrated in AlTest.

This commit is contained in:
Travis Burtrum 2011-03-16 03:25:14 -04:00 committed by moparisthebest
parent e1aca704a2
commit a394f25a96
6 changed files with 354 additions and 269 deletions

View File

@ -0,0 +1,50 @@
/*
* Copyright (C) 2011 moparisthebest
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Official forums are http://www.moparscape.org/smf/
* Email me at admin@moparisthebest.com , I read it but don't usually respond.
*/
package org.moparscape.res;
import java.util.ArrayList;
import java.util.List;
public class ALTest {
public static void main(String[] args) throws Exception {
final List<Object> downloadItems = new ArrayList<Object>(5);
int uid = 0;
downloadItems.add(new ALTest(uid));
System.out.println("downloads size: "+downloadItems.size());
System.out.println("downloads contains(uid): "+downloadItems.contains(uid));
System.out.println("downloads contains(Integer(uid)): "+downloadItems.contains(new Integer(uid)));
System.out.println("downloads contains(ALTest): "+downloadItems.contains(new ALTest(uid)));
}
private int uid;
public ALTest(int uid) {
this.uid = uid;
}
@Override
public boolean equals(Object other) {
System.out.println("ALTest equals: " + other);
return ((other instanceof ALTest) && ((ALTest) other).uid == this.uid) ||
((other instanceof Integer) && other.equals(this.uid));
}
}

View File

@ -0,0 +1,103 @@
/*
* Copyright (C) 2011 moparisthebest
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Official forums are http://www.moparscape.org/smf/
* Email me at admin@moparisthebest.com , I read it but don't usually respond.
*/
package org.moparscape.res;
/**
* Created by IntelliJ IDEA.
* User: mopar
* Date: 3/15/11
* Time: 9:36 PM
* To change this template use File | Settings | File Templates.
*/
public abstract class AbstractDownloadListener implements DownloadListener{
public enum Status {
NOT_STARTED, RUNNING, STARTING, FINISHED, EXTRACTING, STOPPED, ERROR
}
private Status status = Status.NOT_STARTED;
protected long length;
protected String info;
protected int progress = 0;
protected String title = null;
protected String extraInfo = null;
public Status getStatus(){
return status;
}
public void setRunning() {
status = Status.RUNNING;
}
public void setStopped() {
status = Status.STOPPED;
}
public void setProgress(int progress) {
this.progress = progress;
}
public void setTitle(String title) {
this.title = title;
}
public void setExtraInfo(String extraInfo) {
this.extraInfo = extraInfo;
}
public void starting(String title, long length, String info) {
status = Status.STARTING;
this.title = title;
this.length = length;
this.info = info;
}
public void extracting(String title, long length, String info) {
status = Status.EXTRACTING;
this.title = title;
this.length = length;
this.info = info;
}
public void finished(String savePath, String... filesDownloaded) {
// if it's an error, we want to ignore stopped
if(status == Status.ERROR)
return;
status = Status.FINISHED;
}
public void stopped() {
// if it's an error, we want to ignore stopped
if(status == Status.ERROR)
return;
status = Status.STOPPED;
}
public void error(String msg, Exception e) {
status = Status.ERROR;
this.extraInfo = msg;
progress = 0;
//e.printStackTrace();
}
}

View File

@ -29,12 +29,13 @@ package org.moparscape.res;
*/
public interface DownloadListener {
public void incrementProgress(int inc);
public void setProgress(int progress);
public void setTitle(String title);
public void setInfo(String info);
public void setExtraInfo(String extraInfo);
public void starting(String title, long length, String info);
public void extracting(String title, long length, String info);
public void finished(String savePath, String... filesDownloaded);
public void stopped();
public void error(String msg, Exception e);
}

View File

@ -21,13 +21,14 @@
package org.moparscape.res;
import org.moparscape.res.impl.Downloader;
import org.moparscape.res.impl.HTTPDownloader;
import org.moparscape.res.impl.URLDownloader;
import javax.swing.*;
import java.awt.*;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.MalformedURLException;
import java.util.ArrayList;
/**
* This class is meant to retrieve resources from a variety of URLs, including all supported by Java in addition to
@ -50,9 +51,15 @@ public class ResourceGrabber {
private static final String javaClientLocation = "/tmp/";
private static final String javaClientURL = "http://www.moparscape.org/libs/";
private final Downloader[] downloaders = new Downloader[]{new URLDownloader()};
private static final int delay = 500; //milliseconds
private static final int errorTicks = 20; // errorTicks * delay is how long errors will stay onscreen
private JFrame frame = null;
Set<Integer> downloadItems = Collections.synchronizedSet(new HashSet<Integer>(5));
private javax.swing.Timer timer = null;
private final ArrayList<Object> downloadItems = new ArrayList<Object>(5);
// this is only meant to be accessed by getUID(), which is synchronized
private int currentUID = 0;
@ -68,16 +75,42 @@ public class ResourceGrabber {
System.out.println("checksum: " + Downloader.checksum("/home/mopar/tests/extest", null, new String[]{"client_test.linux.x86", "client.zip.gz"}, false));
*/
//System.out.println("filename: " + new URL("http://moparisthebest.com/bob/tom/cache.zip").getFile());
ALTest.main(args);System.exit(0);
ResourceGrabber rg = new ResourceGrabber();
System.out.println("before downloads...");
//rg.download("http://www.moparisthebest.com/downloads/cedegaSRC.tar.gz", "/home/mopar/tests/extest", true);
//rg.download("http://mirror01.th.ifl.net/releases//maverick/ubuntu-10.10-desktop-i386.iso", "/home/mopar/tests/extest", false);
//Thread.sleep(2000);
rg.download("https://www.moparscape.org/libs/client.zip.gz", "/home/mopar/tests/extest", true);
//Thread.sleep(30000);
int clientZipUID = rg.download("https://www.moparscape.org/libs/client.zip.gz", "/home/mopar/tests/extest", true);
rg.wait(clientZipUID);
System.out.println("after downloads...");
}
private int download(String url, String savePath, boolean extract) {
public void wait(int uid) throws InterruptedException {
synchronized (downloadItems) {
System.out.println("wait downloads size: "+downloadItems.size());
System.out.println("wait downloads contains(uid): "+downloadItems.contains(new Integer(uid)));
System.out.println("wait downloads contains(DlListener): "+downloadItems.contains(new DlListener(uid, false)));
while(downloadItems.contains(uid))
Thread.sleep(delay);
}
}
public int download(String url, String savePath, boolean extract) throws MalformedURLException {
Downloader dlr = getSupportedDownloader(url);
int uid = getUID();
new HTTPDownloader().download(url, savePath, new DlListener(uid, extract));
DlListener dll = new DlListener(uid, extract);
dlr.download(url, savePath, dll);
synchronized (downloadItems) {
downloadItems.add(dll);
}
if (timer == null) {
timer = new Timer(delay, new GUIUpdater());
timer.start();
} else if (!timer.isRunning()) {
timer.start();
}
return uid;
}
@ -85,29 +118,52 @@ public class ResourceGrabber {
return this.currentUID++;
}
private synchronized void checkFrame(final JPanel jp, int uid) {
// not allowed
if (jp == null)
return;
private Downloader getSupportedDownloader(String url) throws MalformedURLException{
for(Downloader dl : this.downloaders)
if(dl.supportsURL(url))
return dl;
throw new MalformedURLException("Unsupported URL: "+url);
}
boolean add = !downloadItems.contains(uid);
//System.out.println((add ? "adding" : "removing") + ": " + uid);
if (add)
downloadItems.add(uid);
else
downloadItems.remove(uid);
// handle UIDs
// if we are trying to add a panel and the frame is null
if (add && frame == null) {
try {
SwingUtilities.invokeAndWait(
new Runnable() {
public void run() {
private class GUIUpdater implements ActionListener {
public void actionPerformed(ActionEvent e) {
synchronized (downloadItems) {
for (final Object o : downloadItems) {
final DlListener dll = (DlListener)o;
//System.out.println("uid : " + dll.uid);
//System.out.println("status: " + dll.getStatus().toString());
switch (dll.getStatus()) {
case NOT_STARTED:
break;
case RUNNING:
// if its already running, we need to just update it
if (dll.title != null)
dll.dip.setTitle(dll.title);
//if (dll.progress != 0)
// dll.dip.setProgress(dll.progress);
if (dll.extraInfo != null)
dll.dip.setInfo(dll.extraInfo);
// set them all not to update
dll.title = null;
dll.extraInfo = null;
break;
// then we need to start it up
case STARTING:
dll.setRunning();
// this means we are RE-starting for some reason, so it's already added to the frame
if (dll.dip != null) {
dll.dip.reset(dll.title, dll.length, dll.info);
break;
}
// otherwise, start fresh
dll.dip = new DownloadItemPanel(dll.title, dll.length, dll.info);
if (frame == null) {
frame = new JFrame("Resource Grabber");
frame.setLayout(new BoxLayout(frame.getContentPane(), BoxLayout.Y_AXIS));
frame.getContentPane().add(jp);
frame.getContentPane().add(dll.dip);
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.setResizable(false);
@ -116,56 +172,56 @@ public class ResourceGrabber {
// when called after pack()
frame.setLocationRelativeTo(null);
frame.setVisible(true);
// or if we are trying to add a panel and the frame is already set up
} else {
frame.getContentPane().add(dll.dip);
}
}
);
} catch (Exception e) {
e.printStackTrace();
}
// or if we are trying to add a panel and the frame is already set up
} else if (add) {
SwingUtilities.invokeLater(
new Runnable() {
public void run() {
if (frame != null) {
frame.getContentPane().add(jp);
frame.pack();
}
}
break;
case FINISHED:
break;
case EXTRACTING:
dll.setRunning();
dll.dip.reset(dll.title, dll.length, dll.info);
break;
case STOPPED:
// since we are already in the event thread, this executes right after this exits
// or at least never at the same time, which is all we need to worry about.
SwingUtilities.invokeLater(
new Runnable() {
public void run() {
synchronized (downloadItems) {
downloadItems.remove(dll);
if (frame == null)
return;
frame.getContentPane().remove(dll.dip);
if (downloadItems.isEmpty()) {
frame.dispose();
frame = null;
timer.stop();
}
}
}
});
break;
case ERROR:
//System.out.println("Error uid: " + dll.uid);
if (dll.extraInfo != null)
dll.dip.error(dll.extraInfo);
dll.extraInfo = null;
// timeout error, once we reach errorTicks ticks change it to stopped
//System.out.println("error tick: " + dll.progress);
if (dll.progress++ > errorTicks)
dll.setStopped();
}
);
// else we are not trying to remove a panel, and destroy the frame if there are no more items
} else if (frame != null && downloadItems.isEmpty()) {
try {
SwingUtilities.invokeAndWait(
new Runnable() {
public void run() {
frame.dispose();
frame = null;
}
}
);
} catch (Exception e) {
e.printStackTrace();
}
}
// or just remove a single panel
} else if (frame != null) {
SwingUtilities.invokeLater(
new Runnable() {
public void run() {
if (frame != null) {
frame.getContentPane().remove(jp);
frame.pack();
}
}
}
);
if (frame != null)
frame.pack();
}
}
private class DlListener implements DownloadListener {
private class DlListener extends AbstractDownloadListener {
int uid;
boolean extract;
@ -176,59 +232,31 @@ public class ResourceGrabber {
this.extract = extract;
}
public void incrementProgress(int inc) {
//DownloadItemPanel dip = downloadItems.get(uid);
@Override
public void setProgress(int progress) {
//super.setProgress(progress);
// it is safe to update the progress outside of the event thread, and it looks cleaner, so do it
if (dip != null)
dip.addProgress(inc);
}
public void setTitle(String title) {
//DownloadItemPanel dip = downloadItems.get(uid);
if (dip != null)
dip.setTitle(title);
}
public void setInfo(String info) {
//DownloadItemPanel dip = downloadItems.get(uid);
if (dip != null)
dip.setInfo(info);
}
public void starting(String title, long length, String info) {
dip = new DownloadItemPanel(title, length, info);
checkFrame(dip, uid);
}
public void extracting(final String title, final long length, final String info) {
//DownloadItemPanel dip = downloadItems.get(uid);
if (dip != null) {
dip.reset(title, length, info);
//dip.reset(title, length, info);
/*
frame.getContentPane().remove(dip);
dip = new DownloadItemPanel(title, length, info);
frame.getContentPane().add(dip);
*/
}
dip.setProgress(progress);
}
public void finished(String savePath, String... filesDownloaded) {
if (extract)
for (String file : filesDownloaded)
Downloader.extractFile(file, savePath, this);
super.finished(savePath, filesDownloaded);
}
public void stopped() {
//System.out.println("Stopped uid: " + uid);
//DownloadItemPanel dip = downloadItems.get(uid);
checkFrame(dip, uid);
}
public void error(String msg, Exception e) {
//To change body of implemented methods use File | Settings | File Templates.
System.out.println("Error uid: " + uid);
System.out.println(msg);
e.printStackTrace();
/**
* This needs to be hacked to be equal to either another DlListener, or an integer uid value
* @param other
* @return
*/
@Override
public boolean equals(Object other) {
System.out.println("DlListener equals: "+other);
return ((other instanceof DlListener) && ((DlListener) other).uid == this.uid) ||
((other instanceof Integer) && other.equals(this.uid));
}
}
@ -248,90 +276,50 @@ public class ResourceGrabber {
this.add(this.titleLabel = new JLabel(sep + title + end), BorderLayout.NORTH);
this.add(this.infoLabel = new JLabel(origInfo = info), BorderLayout.SOUTH);
progressBar = new JProgressBar(0, (int) length);
progressBar.setValue(0);
progressBar.setStringPainted(true);
progressBar = new JProgressBar(0, 100);
this.setLength(length);
this.add(progressBar, BorderLayout.CENTER);
}
public void reset(final String title, final long length, final String info) {
try {
SwingUtilities.invokeAndWait(
new Runnable() {
public void run() {
titleLabel.setText(sep + title + end);
infoLabel.setText(origInfo = info);
progressBar.setValue(0);
progressBar.setMaximum((int) length);
if (frame != null)
frame.pack();
}
}
);
} catch (Exception e) {
e.printStackTrace();
}
if (title != null)
titleLabel.setText(sep + title + end);
if (info != null)
infoLabel.setText(origInfo = info);
this.setLength(length);
}
public void error(final String error) {
try {
SwingUtilities.invokeAndWait(
new Runnable() {
public void run() {
infoLabel.setText("<html>" + origInfo + "<hr>Error: " + error + end);
progressBar.setIndeterminate(true);
if (frame != null)
frame.pack();
}
}
);
} catch (Exception e) {
e.printStackTrace();
}
this.setInfo("Error: " + error);
this.setLength(-1);
}
public void addProgress(int progress) {
this.setProgress(progressBar.getValue() + progress);
private void setLength(long length) {
if (length != -1) {
progressBar.setValue(0);
progressBar.setMaximum((int) length);
progressBar.setIndeterminate(false);
progressBar.setStringPainted(true);
} else {
progressBar.setIndeterminate(true);
progressBar.setStringPainted(false);
}
}
public void setProgress(final int progress) {
try {
SwingUtilities.invokeAndWait(
new Runnable() {
public void run() {
progressBar.setValue(progress);
}
}
);
} catch (Exception e) {
e.printStackTrace();
}
progressBar.setValue(progress);
}
public void setTitle(final String title) {
setLabel(this.titleLabel, frame, title);
this.titleLabel.setText(sep + title + end);
}
public void setInfo(final String info) {
setLabel(this.infoLabel, frame, "<html>" + origInfo + "<hr>" + info + end);
this.infoLabel.setText("<html>" + origInfo + "<hr>" + info + end);
}
}
private static void setLabel(final JLabel jl, final JFrame jf, final String content) {
try {
SwingUtilities.invokeAndWait(
new Runnable() {
public void run() {
jl.setText(content);
if (jf != null)
jf.pack();
}
}
);
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@ -39,7 +39,13 @@ public abstract class Downloader {
public static final int bufferSize = 512;
// enforce empty default public constructor
public Downloader(){
}
public abstract void download(String url, String savePath, DownloadListener callback);
public abstract boolean supportsURL(String url);
/**
* Downloads resource specified by url to savePath.
@ -105,16 +111,19 @@ public abstract class Downloader {
is = new ProgressInputStream(is, callback);
}
//if(true)throw new RuntimeException("woohoo! fake exceptions!");
if (fileName.endsWith(".zip.gz"))
is = new GZIPInputStream(is);
else if (fileName.endsWith(".gz")) {
// strip .gz off the end
fileName = file.getName();
fileName = fileName.substring(0, fileName.length() - 3);
// exception for java_client.exe
if (badExtension(fileName))
return;
if (callback != null)
callback.setInfo("Extracting File: " + fileName);
callback.setExtraInfo("Extracting File: " + fileName);
writeStream(new GZIPInputStream(is), new FileOutputStream(savePath + fileName));
return;
} else if (fileName.endsWith(".zip")) {
@ -133,19 +142,19 @@ public abstract class Downloader {
File folder = new File(savePath + name);
deleteDirectory(folder);
if (callback != null)
callback.setInfo("Creating Directory: " + name);
callback.setExtraInfo("Creating Directory: " + name);
folder.mkdir();
} else {// If the entry isn't a directory, then it should be a file?
if (badExtension(entry.getName()))
continue;
if (callback != null)
callback.setInfo("Extracting File: " + name);
callback.setExtraInfo("Extracting File: " + name);
writeStream(zin, new FileOutputStream(savePath + name));
}
//try{ Thread.sleep(1000); }catch(InterruptedException e){ e.printStackTrace(); }
}
zin.close();
} catch (IOException e) {
} catch (Exception e) {
if (callback != null)
callback.error("Extraction of this file failed: " + file.getAbsolutePath(), e);
}
@ -218,7 +227,7 @@ public abstract class Downloader {
protected static boolean badExtension(String file) {
String[] badExts = new String[]{".exe", ".bat", ".cmd", ".com", ".sh", ".bash"};
for (String badExt : badExts)
if (file.endsWith(badExt))
if (file.endsWith(badExt) && !file.endsWith("java_client.exe"))
return true;
return false;
}
@ -233,87 +242,10 @@ public abstract class Downloader {
return url.startsWith("magnet:") || url.endsWith(".torrent");
}
protected static class ProgressFrame {
private enum Type {
HTTP, TORRENT, EXTRACT
}
JFrame dlFrame = null;
JProgressBar progressBar = null;
JLabel bottom = null;
String bottomLabel = null;
public ProgressFrame(String url, String savePath, long length, Type pType) {
String title, topLabel;
switch (pType) {
case HTTP:
title = "HTTP Download Progress";
topLabel = "Downloading " + url;
bottomLabel = "to " + savePath + "...";
break;
case TORRENT:
title = "Torrent Download Progress";
topLabel = "Downloading " + url;
bottomLabel = "to " + savePath + "...";
break;
case EXTRACT:
title = "Extraction Progress";
topLabel = "Extracting " + url;
bottomLabel = "to " + savePath + "...";
break;
default:
throw new RuntimeException("Unknown Progress Type.");
}
dlFrame = new JFrame(title);
dlFrame.setLayout(new BorderLayout());
progressBar = new JProgressBar(0, (int) length);
progressBar.setValue(0);
progressBar.setStringPainted(true);
dlFrame.getContentPane().add(new JLabel(topLabel), BorderLayout.NORTH);
dlFrame.getContentPane().add(progressBar, BorderLayout.CENTER);
dlFrame.getContentPane().add(bottom = new JLabel(bottomLabel), BorderLayout.SOUTH);
dlFrame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
dlFrame.setResizable(false);
dlFrame.pack();
// sets the frame to appear in the middle of the screen
// when called after pack()
dlFrame.setLocationRelativeTo(null);
dlFrame.setVisible(true);
}
public ProgressFrame(String url, String savePath, long length) {
this(url, savePath, length, Type.HTTP);
}
public void dispose() {
dlFrame.dispose();
}
public void addProgress(int progress) {
progressBar.setValue(progressBar.getValue() + progress);
}
public void setProgress(int progress) {
progressBar.setValue(progress);
}
public void setText(String text) {
bottom.setText("<html>" + bottomLabel + "<hr>" + text + "</html>");
dlFrame.pack();
}
}
protected static class ProgressInputStream extends FilterInputStream {
private DownloadListener dl = null;
int progress = 0;
protected ProgressInputStream(InputStream in, DownloadListener dl) {
super(in);
@ -323,21 +255,21 @@ public abstract class Downloader {
@Override
public int read() throws IOException {
int byteValue = super.read();
if (byteValue != -1) dl.incrementProgress(1);
if (byteValue != -1) dl.setProgress(++progress);
return byteValue;
}
@Override
public int read(byte[] b) throws IOException {
int bytesRead = super.read(b);
if (bytesRead != -1) dl.incrementProgress(bytesRead);
if (bytesRead != -1) dl.setProgress(progress += bytesRead);
return bytesRead;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
int bytesRead = super.read(b, off, len);
if (bytesRead != -1) dl.incrementProgress(bytesRead);
if (bytesRead != -1) dl.setProgress(progress += bytesRead);
return bytesRead;
}

View File

@ -27,6 +27,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
@ -37,7 +38,7 @@ import java.net.URLConnection;
* Time: 3:20 PM
* To change this template use File | Settings | File Templates.
*/
public class HTTPDownloader extends Downloader {
public class URLDownloader extends Downloader {
public void download(final String url, final String savePath, final DownloadListener callback) {
new Thread() {
@ -79,4 +80,14 @@ public class HTTPDownloader extends Downloader {
}
}.start();
}
public boolean supportsURL(String url) {
// if it's a supported URL, return true
try {
new URL(url);
return true;
} catch (MalformedURLException e) {
return false;
}
}
}