Skip to content

Commit

Permalink
Merge pull request #8 from honokaBiu/master
Browse files Browse the repository at this point in the history
implement FileDownloader by Korben
  • Loading branch information
conf1102 authored Mar 11, 2017
2 parents 4116f15 + d4c6bf6 commit e02befe
Show file tree
Hide file tree
Showing 9 changed files with 437 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package org.korben.coderising.download;

import java.io.IOException;
import java.io.RandomAccessFile;
import org.korben.coderising.download.api.Connection;
import org.korben.coderising.download.api.ConnectionException;
import org.korben.coderising.download.api.ConnectionManager;
import org.korben.coderising.download.api.DownloadListener;

public class DownloadThread extends Thread {

private int endPos;
private int startPos;
private String url;
private String destFilePath;
private ConnectionManager connManager;
private DownloadListener downloadListener;

public DownloadThread(ConnectionManager connManager, String url, int startPos, int endPos, String destFilePath,
DownloadListener downloadListener) {

this.url = url;
this.endPos = endPos;
this.startPos = startPos;
this.connManager = connManager;
this.destFilePath = destFilePath;
this.downloadListener = downloadListener;
}

@Override
public void run() {
Connection conn = null;
RandomAccessFile randomAccessFile = null;
try {
doLog("BIN");
conn = connManager.open(url, startPos, endPos);
byte[] read = conn.read(startPos, endPos);
String _filePath = destFilePath;
if (_filePath == null || _filePath.length() == 0) {
_filePath = conn.getFileName();
}
randomAccessFile = new RandomAccessFile(_filePath, "rw");
randomAccessFile.seek(startPos);
randomAccessFile.write(read);
doLog("END");
} catch (IOException e) {
doLog("EXP");
e.printStackTrace();
} catch (ConnectionException e) {
doLog("EXP");
e.printStackTrace();
} finally {
if (randomAccessFile != null) {
try {
randomAccessFile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (conn != null) {
conn.close();
}
if (downloadListener != null) {
downloadListener.notifyFinished();
}
}
}

private void doLog(String action) {
System.out.println(
"*********** " + action
+ " ["
+ startPos
+ "-"
+ endPos
+ "]"
+ " ***********");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package org.korben.coderising.download;

import java.util.concurrent.atomic.AtomicInteger;
import org.korben.coderising.download.api.ConnectionException;
import org.korben.coderising.download.api.ConnectionManager;
import org.korben.coderising.download.api.DownloadListener;

public class FileDownloader {

private String url;

private DownloadListener listener;

private ConnectionManager cm;

private AtomicInteger atomicInteger;

public FileDownloader(String _url) {
this.url = _url;
atomicInteger = new AtomicInteger();
}

/**
* 在这里实现你的代码, 注意: 需要用多线程实现下载
* 这个类依赖于其他几个接口, 你需要写这几个接口的实现代码
* (1) ConnectionManager , 可以打开一个连接,通过Connection可以读取其中的一段(用startPos, endPos来指定)
* (2) DownloadListener, 由于是多线程下载, 调用这个类的客户端不知道什么时候结束,所以你需要实现当所有
* 线程都执行完以后, 调用listener的notifiedFinished方法, 这样客户端就能收到通知。
* 具体的实现思路:
* 1. 需要调用ConnectionManager的open方法打开连接, 然后通过Connection.getContentLength方法获得文件的长度
* 2. 至少启动3个线程下载, 注意每个线程需要先调用ConnectionManager的open方法
* 然后调用read方法, read方法中有读取文件的开始位置和结束位置的参数, 返回值是byte[]数组
* 3. 把byte数组写入到文件中
* 4. 所有的线程都下载完成以后, 需要调用listener的notifiedFinished方法
*
* 下面的代码是示例代码, 也就是说只有一个线程, 你需要改造成多线程的。
*/
public void execute() {
try {

int threadCount = 5;
int length = this.cm.getContentLength(this.url);
for (int i = 0; i < threadCount; i++) {

int threadLoadLength = length / threadCount;
int startPos = threadLoadLength * i;
int endPos;
if (i != threadCount - 1) {
endPos = threadLoadLength * (i + 1) - 1;
} else {
endPos = length - 1;
}
atomicInteger.getAndIncrement();
new DownloadThread(cm, this.url, startPos, endPos, null, new DownloadListener() {
@Override
public void notifyFinished() {
if (atomicInteger.decrementAndGet() == 0) {
if (FileDownloader.this.listener != null) {
FileDownloader.this.listener.notifyFinished();
}
}
}
}).start();
}
} catch (ConnectionException e) {
e.printStackTrace();
}
}

public void setConnectionManager(ConnectionManager ucm) {
this.cm = ucm;
}

public DownloadListener getListener() {
return this.listener;
}

public void setListener(DownloadListener listener) {
this.listener = listener;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package org.korben.coderising.download;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.korben.coderising.download.api.ConnectionManager;
import org.korben.coderising.download.api.DownloadListener;
import org.korben.coderising.download.impl.ConnectionManagerImpl;

public class FileDownloaderTest {

boolean downloadFinished = false;

@Before
public void setUp() throws Exception {
}

@After
public void tearDown() throws Exception {
}

@Test
public void testDownload() {

String url = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1489721424&di=1fda6467501ab1d5e5bff43e801d14ee&imgtype=jpg&er=1&src=http%3A%2F%2Fimg4.duitang.com%2Fuploads%2Fitem%2F201507%2F30%2F20150730163204_A24MX.thumb.700_0.jpeg";
//String url = "http://apache.fayea.com/maven/maven-3/3.3.9/binaries/apache-maven-3.3.9-bin.tar.gz";

FileDownloader downloader = new FileDownloader(url);

ConnectionManager cm = new ConnectionManagerImpl();
downloader.setConnectionManager(cm);

downloader.setListener(new DownloadListener() {
@Override
public void notifyFinished() {
downloadFinished = true;
}
});

downloader.execute();

// 等待多线程下载程序执行完毕
while (!downloadFinished) {
try {
System.out.println("还没有下载完成,休眠五秒");
//休眠5秒
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("下载完成!");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.korben.coderising.download.api;

import java.io.IOException;

public interface Connection {
/**
* 给定开始和结束位置, 读取数据, 返回值是字节数组
*
* @param startPos 开始位置, 从0开始
* @param endPos 结束位置
* @return 读取的字节数组
*/
byte[] read(int startPos, int endPos) throws IOException;

/**
* 得到数据内容的长度
*
* @return 数据内容长度
*/
int getContentLength();

/**
* 关闭连接
*/
void close();

/**
* 获取下载文件的文件名
*
* @return 文件名
*/
String getFileName();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.korben.coderising.download.api;

public class ConnectionException extends Exception {
public ConnectionException(Exception e) {
super(e);
}

public ConnectionException(String msg) {
super(msg);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.korben.coderising.download.api;

public interface ConnectionManager {
/**
* 给定一个url , 打开一个连接
*
* @param url 连接地址
* @param startPos 读取文件的起始位置
* @param endPos 读取文件的结束位置
* @return 连接
*/
Connection open(String url, int startPos, int endPos) throws ConnectionException;

/**
* 获取文件长度
*
* @param url 连接地址
* @return 文件长度
*/
int getContentLength(String url) throws ConnectionException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.korben.coderising.download.api;

public interface DownloadListener {
void notifyFinished();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package org.korben.coderising.download.impl;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import org.korben.coderising.download.api.Connection;

public class ConnectionImpl implements Connection {

private static final int BUFFER_SIZE = 4096;
private HttpURLConnection httpConn;
private String fileUrl;
private InputStream inputStream;

public ConnectionImpl(HttpURLConnection httpConn, String fileUrl) {
this.httpConn = httpConn;
this.fileUrl = fileUrl;
}

@Override
public byte[] read(int startPos, int endPos) throws IOException {
if (endPos < startPos) {
throw new IllegalArgumentException("argument endPos[" + endPos + "] less than startPos[" + startPos + "]");
}
int bytesNeed2Read = endPos - startPos + 1;
if (bytesNeed2Read > getContentLength()) {
throw new IllegalArgumentException(
"endPos[" + endPos + "] is bigger than content length[" + getContentLength() + "]");
}

inputStream = httpConn.getInputStream();

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[Math.min(bytesNeed2Read, BUFFER_SIZE)];
int read;

long startTime = System.currentTimeMillis();
final long progressInterval = 2000;
while ((read = inputStream.read(buffer)) != -1) {
byteArrayOutputStream.write(buffer, 0, read);

if (System.currentTimeMillis() - startTime > progressInterval) {
startTime = System.currentTimeMillis();
System.out.println(String.format(Thread.currentThread().getName() +
" [%.2f%%]", byteArrayOutputStream.size() * 100.0 / bytesNeed2Read)
);
}
}
System.out.println(String.format(Thread.currentThread().getName() + " [%.2f%%]", 100.0));
System.out.println("bytes read: " + byteArrayOutputStream.size());

return byteArrayOutputStream.toByteArray();
}

@Override
public int getContentLength() {
if (httpConn != null) {
return httpConn.getContentLength();
}
return 0;
}

@Override
public void close() {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (httpConn != null) {
httpConn.disconnect();
}
}

@Override
public String getFileName() {
String disposition = httpConn.getHeaderField("Content-Disposition");
if (disposition != null) {
// extracts file name from header field
int index = disposition.indexOf("filename=");
if (index > 0) {
return disposition.substring(index + 10,
disposition.length() - 1);
}
}
// extracts file name from URL
return fileUrl.substring(fileUrl.lastIndexOf("/") + 1,
fileUrl.length());
}
}
Loading

0 comments on commit e02befe

Please sign in to comment.