package canghailan;
import canghailan.util.StopWatch;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* User: canghailan
* Date: 11-12-9
* Time: 下午2:03
*/
public class Downloader implements Callable<File> {
protected int connectTimeout = 30 * 1000; // 连接超时:30s
protected int readTimeout = 1 * 1000 * 1000; // IO超时:1min
protected int speedRefreshInterval = 500; // 即时速度刷新最小间隔:500ms
protected byte[] buffer;
private URL url;
private File file;
private float averageSpeed;
private float currentSpeed;
public Downloader() {
buffer = new byte[8 * 1024]; // IO缓冲区:8KB
}
public void setUrlAndFile(URL url, File file) {
this.url = url;
this.file = autoRenameIfExist(file);
this.averageSpeed = 0;
this.currentSpeed = 0;
}
public URL getUrl() {
return url;
}
public File getFile() {
return file;
}
public float getAverageSpeed() {
return averageSpeed;
}
public float getCurrentSpeed() {
return currentSpeed;
}
@Override
public File call() throws Exception {
StopWatch watch = new StopWatch();
watch.start();
InputStream in = null;
OutputStream out = null;
try {
URLConnection conn = url.openConnection();
conn.setConnectTimeout(connectTimeout);
conn.setReadTimeout(readTimeout);
conn.connect();
in = conn.getInputStream();
out = new FileOutputStream(file);
int time = 0;
int bytesInTime = 0;
for (; ; ) {
watch.split();
int bytes = in.read(buffer);
if (bytes == -1) {
break;
}
out.write(buffer, 0, bytes);
time += watch.getTimeFromSplit();
if (time >= speedRefreshInterval) {
currentSpeed = getSpeed(bytesInTime, time);
time = 0;
bytesInTime = 0;
}
}
} catch (IOException e) {
file.delete();
throw e;
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
}
}
if (out != null) {
try {
out.flush();
out.close();
} catch (IOException e) {
}
}
}
watch.stop();
averageSpeed = getSpeed(file.length(), watch.getTime());
return file;
}
private static float getSpeed(long bytesInTime, long time) {
return (float) bytesInTime / 1024 / ((float) time / 1000);
}
private static String getExtension(String string) {
int lastDotIndex = string.lastIndexOf('.');
// . ..
if (lastDotIndex > 0) {
return string.substring(lastDotIndex + 1);
} else {
return "";
}
}
private static File autoRenameIfExist(File file) {
if (file.exists()) {
String path = file.getAbsolutePath();
String extension = getExtension(path);
int baseLength = path.length();
if (extension.length() > 0) {
baseLength = path.length() - extension.length() - 1;
}
StringBuilder buffer = new StringBuilder(path);
for (int index = 1; index < Integer.MAX_VALUE; ++index) {
buffer.setLength(baseLength);
buffer.append('(').append(index).append(')');
if (extension.length() > 0) {
buffer.append('.').append(extension);
}
file = new File(buffer.toString());
if (!file.exists()) {
break;
}
}
}
return file;
}
public static void main(String[] args) throws IOException, ExecutionException, InterruptedException {
URL url = new URL("http://www.google.com.hk/");
File file = new File("/home/canghailan/google.html");
ExecutorService executorService = Executors.newSingleThreadExecutor();
Downloader downloader = new Downloader();
downloader.setUrlAndFile(url, file);
File downloadFIle = executorService.submit(downloader).get();
System.out.println("download " + downloadFIle.getName() +
" from " + url +
" @" + downloader.getAverageSpeed() + "KB/s");
}
}
package canghailan.util;
import java.util.concurrent.TimeUnit;
/**
* User: canghailan
* Date: 11-12-9
* Time: 下午3:45
* <pre>
* RUNNING:
* startTime split now
* |<- getSplitTime() ->|<- getTimeFromSplit() ->|
* |<- getTime() ->|
* <pre/>
* <pre>
* STOPPED:
* startTime stop now
* |<- getTime() ->|
* <pre/>
*/
public class StopWatch {
private long startTime;
private long stopTime;
private State state;
private boolean split;
public StopWatch() {
reset();
}
public void start() {
if (state == State.UNSTARTED) {
startTime = System.nanoTime();
state = State.RUNNING;
return;
}
throw new RuntimeException("Stopwatch already started or stopped.");
}
public void stop() {
switch (state) {
case RUNNING: {
stopTime = System.nanoTime();
}
case SUSPENDED: {
state = State.STOPPED;
split = false;
return;
}
}
throw new RuntimeException("Stopwatch is not running.");
}
public void reset() {
state = State.UNSTARTED;
split = false;
}
public void split() {
if (state == State.RUNNING) {
stopTime = System.nanoTime();
split = true;
return;
}
throw new RuntimeException("Stopwatch is not running.");
}
public void suspend() {
if (state == State.RUNNING) {
stopTime = System.nanoTime();
state = State.SUSPENDED;
return;
}
throw new RuntimeException("Stopwatch must be running to suspend.");
}
public void resume() {
if (state == State.SUSPENDED) {
startTime += System.nanoTime() - stopTime;
state = State.RUNNING;
return;
}
throw new RuntimeException("Stopwatch must be suspended to resume.");
}
public long getTime() {
return TimeUnit.NANOSECONDS.toMillis(getNanoTime());
}
public long getNanoTime() {
switch (state) {
case RUNNING: {
return System.nanoTime() - startTime;
}
case STOPPED:
case SUSPENDED: {
return stopTime - startTime;
}
case UNSTARTED: {
return 0;
}
}
throw new RuntimeException("Should never get here.");
}
public long getSplitTime() {
return TimeUnit.NANOSECONDS.toMillis(getSplitNanoTime());
}
public long getSplitNanoTime() {
if (split) {
return stopTime - startTime;
}
throw new RuntimeException("Stopwatch must be running and split to get the split time.");
}
public long getTimeFromSplit() {
return TimeUnit.NANOSECONDS.toMillis(getNanoTimeFromSplit());
}
public long getNanoTimeFromSplit() {
if (state == State.RUNNING && split) {
return System.nanoTime() - stopTime;
}
throw new RuntimeException("Stopwatch must be running and split to get the time from split.");
}
enum State {
UNSTARTED,
RUNNING,
STOPPED,
SUSPENDED
}
}