diff --git a/src/main/java/com/ilummc/eagletdl/AlreadyStartException.java b/src/main/java/com/ilummc/eagletdl/AlreadyStartException.java new file mode 100644 index 0000000..13919a0 --- /dev/null +++ b/src/main/java/com/ilummc/eagletdl/AlreadyStartException.java @@ -0,0 +1,4 @@ +package com.ilummc.eagletdl; + +public class AlreadyStartException extends RuntimeException { +} diff --git a/src/main/java/com/ilummc/eagletdl/CompleteEvent.java b/src/main/java/com/ilummc/eagletdl/CompleteEvent.java new file mode 100644 index 0000000..839625b --- /dev/null +++ b/src/main/java/com/ilummc/eagletdl/CompleteEvent.java @@ -0,0 +1,19 @@ +package com.ilummc.eagletdl; + +public class CompleteEvent { + private EagletTask task; + private boolean success; + + CompleteEvent(EagletTask task, boolean success) { + this.task = task; + this.success = success; + } + + public boolean isSuccess() { + return success; + } + + public EagletTask getTask() { + return task; + } +} diff --git a/src/main/java/com/ilummc/eagletdl/ConnectedEvent.java b/src/main/java/com/ilummc/eagletdl/ConnectedEvent.java new file mode 100644 index 0000000..5299f27 --- /dev/null +++ b/src/main/java/com/ilummc/eagletdl/ConnectedEvent.java @@ -0,0 +1,27 @@ +package com.ilummc.eagletdl; + +public class ConnectedEvent { + + private long contentLength; + private EagletTask task; + + public ConnectedEvent(long length, EagletTask task) { + this.contentLength = length; + this.task = task; + } + + /** + * Get the length of the download task. + *
+ * If the length is -1, this task cannot be downloaded in multiple threads.
+ *
+ * @return length
+ */
+ public long getContentLength() {
+ return contentLength;
+ }
+
+ public EagletTask getTask() {
+ return task;
+ }
+}
diff --git a/src/main/java/com/ilummc/eagletdl/DoNotSupportMultipleThreadException.java b/src/main/java/com/ilummc/eagletdl/DoNotSupportMultipleThreadException.java
new file mode 100644
index 0000000..0c59692
--- /dev/null
+++ b/src/main/java/com/ilummc/eagletdl/DoNotSupportMultipleThreadException.java
@@ -0,0 +1,4 @@
+package com.ilummc.eagletdl;
+
+public class DoNotSupportMultipleThreadException extends RuntimeException {
+}
diff --git a/src/main/java/com/ilummc/eagletdl/Eaglet.java b/src/main/java/com/ilummc/eagletdl/Eaglet.java
new file mode 100644
index 0000000..7e000b3
--- /dev/null
+++ b/src/main/java/com/ilummc/eagletdl/Eaglet.java
@@ -0,0 +1,22 @@
+package com.ilummc.eagletdl;
+
+/**
+ * Test class
+ */
+public class Eaglet {
+
+ public static void main(String[] args) {
+ //new EagletTask().url("http://sgp-ping.vultr.com/vultr.com.100MB.bin")
+ new EagletTask().url("https://gitee.com/bkm016/TabooLibCloud/raw/master/TabooMenu/TabooMenu.jar")
+ .file("F:\\test.dl")
+ .setThreads(1)
+ .readTimeout(1000)
+ .connectionTimeout(1000)
+ .maxRetry(30)
+ .setOnConnected(event -> System.out.println(event.getContentLength()))
+ .setOnProgress(event -> System.out.println(event.getSpeedFormatted() + " " + event.getPercentageFormatted()))
+ .setOnComplete(event -> System.out.println("Complete"))
+ .start();
+ }
+
+}
diff --git a/src/main/java/com/ilummc/eagletdl/EagletHandler.java b/src/main/java/com/ilummc/eagletdl/EagletHandler.java
new file mode 100644
index 0000000..ef63e4a
--- /dev/null
+++ b/src/main/java/com/ilummc/eagletdl/EagletHandler.java
@@ -0,0 +1,8 @@
+package com.ilummc.eagletdl;
+
+@FunctionalInterface
+public interface EagletHandler
+ * 开始下载文件
+ */
+ public EagletTask start() {
+ // create thread pool for download
+ executorService = Executors.newFixedThreadPool(threadAmount);
+ // check if is already running
+ if (running) throw new AlreadyStartException();
+ // start the monitor thread
+ monitor = new Thread(() -> {
+ lock.lock();
+ // fire a new start event
+ if (onStart != null) onStart.handle(new StartEvent(this));
+ try {
+ // create the target file
+ if (!dest.exists()) dest.createNewFile();
+ HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+ // set the connection properties
+ httpHeader.forEach(connection::addRequestProperty);
+ connection.setRequestMethod(requestMethod);
+ connection.setConnectTimeout(30000);
+ connection.setReadTimeout(30000);
+ connection.connect();
+ contentLength = connection.getContentLengthLong();
+ // fire a new connected event
+ // contains connection properties
+ if (onConnected != null) onConnected.handle(new ConnectedEvent(contentLength, this));
+ // if this is an unknown length task
+ if (contentLength == -1 || threadAmount == 1) {
+ // pass the connection instance to this new thread
+ SingleThreadDownload download = new SingleThreadDownload(connection, dest, this);
+ executorService.execute(download);
+ long last = 0;
+ while (true) {
+ Thread.sleep(1000);
+ // check the progress
+ long progress = download.getCurrentProgress();
+ // fire a new progress event
+ if (onProgress != null)
+ onProgress.handle(new ProgressEvent(progress - last, this,
+ ((double) progress) / Math.max((double) contentLength, 0D)));
+ last = progress;
+ // check complete
+ if (last == contentLength || download.isComplete()) {
+ break;
+ }
+ }
+ // close the thread pool, release resources
+ executorService.shutdown();
+ // change the running flag to false
+ running = false;
+ } else {
+ List
+ * If the hash check failed, an error event will be fired.
+ *
+ * @param sha256 file sha1
+ * @return task instance
+ */
+ public EagletTask sha256(String sha256) {
+ this.sha256 = sha256;
+ return this;
+ }
+
+ /**
+ * Set the sha1 hash of the download file. Case is not sensitive.
+ *
+ * If the hash check failed, an error event will be fired.
+ *
+ * @param sha1 file sha1
+ * @return task instance
+ */
+ public EagletTask sha1(String sha1) {
+ this.sha1 = sha1;
+ return this;
+ }
+
+ /**
+ * Set the md5 hash of the download file. Case is not sensitive.
+ *
+ * If the hash check failed, an error event will be fired.
+ *
+ * @param md5 file md5
+ * @return task instance
+ */
+ public EagletTask md5(String md5) {
+ this.md5 = md5;
+ return this;
+ }
+
+ /**
+ * Set the max blocked time per download thread.
+ *
+ * If the thread blocks exceeded the provided time, this thread will re-start the task.
+ *
+ * @param maxBlockingTime time
+ * @return task instance
+ */
+ private EagletTask maxBlocking(long maxBlockingTime) {
+ this.maxBlockingTime = maxBlockingTime;
+ return this;
+ }
+
+ /**
+ * Set the progress handler
+ *
+ * This handler will be called every 1000 milli seconds.
+ *
+ * 设置处理进度的时间监听器。该监听器的 handle 方法每秒调用一次。
+ *
+ * @param onProgress handler
+ * @return task instance
+ */
+ public EagletTask setOnProgress(EagletHandler
+ * This will be called when the connection is established
+ *
+ * Async call
+ *
+ * @param onConnected onConnected event handler
+ * @return task instance
+ */
+ public EagletTask setOnConnected(EagletHandler
+ * This handler will be called when everything is complete, and the downloaded file is available
+ *
+ * Async call
+ *
+ * @param onComplete the handler
+ * @return task instance
+ */
+ public EagletTask setOnComplete(EagletHandler
+ * This handler will be called when the
+ * Async call
+ *
+ * @param onStart the handler
+ * @return task instance
+ */
+ public EagletTask setOnStart(EagletHandler
+ * This handler will be called when an exception is thrown
+ *
+ * Async call
+ *
+ * @param onError the handler
+ * @return task instance
+ */
+ public EagletTask setOnError(EagletHandlerGET
+ *
+ * @param requestMethod the request method
+ * @return task instance
+ */
+ public EagletTask requestMethod(String requestMethod) {
+ this.requestMethod = requestMethod;
+ return this;
+ }
+
+ /**
+ * Set the complete event handler
+ * start
method is called
+ * X.00 MiB
, Y.50 GiB
, etc.
+ *
+ * @return formatted speed string
+ */
+ public String getSpeedFormatted() {
+ return format(getSpeed());
+ }
+
+ private static String formatDouble(double d) {
+ return new DecimalFormat("0.00").format(d);
+ }
+
+ public static String format(long l) {
+ if (l < 1024) return l + " B";
+ if (l < 1024 * 1024) return formatDouble((double) l / 1024D) + " KiB";
+ if (l < 1024 * 1024 * 1024) return formatDouble((double) l / (1024D * 1024D)) + " MiB";
+ if (l < 1024 * 1024 * 1024 * 1024L) return formatDouble((double) l / (1024D * 1024D * 1024)) + " GiB";
+ return "";
+ }
+}
diff --git a/src/main/java/com/ilummc/eagletdl/RetryFailedException.java b/src/main/java/com/ilummc/eagletdl/RetryFailedException.java
new file mode 100644
index 0000000..e8a022d
--- /dev/null
+++ b/src/main/java/com/ilummc/eagletdl/RetryFailedException.java
@@ -0,0 +1,14 @@
+package com.ilummc.eagletdl;
+
+public class RetryFailedException extends RuntimeException {
+
+ private EagletTask task;
+
+ RetryFailedException(EagletTask task) {
+ this.task = task;
+ }
+
+ public EagletTask getTask() {
+ return task;
+ }
+}
diff --git a/src/main/java/com/ilummc/eagletdl/SingleThreadDownload.java b/src/main/java/com/ilummc/eagletdl/SingleThreadDownload.java
new file mode 100644
index 0000000..137762d
--- /dev/null
+++ b/src/main/java/com/ilummc/eagletdl/SingleThreadDownload.java
@@ -0,0 +1,50 @@
+package com.ilummc.eagletdl;
+
+import java.io.*;
+import java.net.HttpURLConnection;
+
+class SingleThreadDownload implements Runnable {
+
+ private HttpURLConnection connection;
+ private File target;
+ private EagletTask task;
+
+ private transient long currentProgress = 0, lastUpdateTime = System.currentTimeMillis();
+
+ private transient boolean complete = false;
+
+ SingleThreadDownload(HttpURLConnection connection, File target, EagletTask task) {
+ this.connection = connection;
+ this.target = target;
+ this.task = task;
+ }
+
+ long getLastUpdateTime() {
+ return lastUpdateTime;
+ }
+
+ long getCurrentProgress() {
+ return currentProgress;
+ }
+
+ public boolean isComplete() {
+ return complete;
+ }
+
+ @Override
+ public void run() {
+ byte[] buf = new byte[1024];
+ int len = 0;
+ try (BufferedInputStream stream = new BufferedInputStream(connection.getInputStream());
+ BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(target))) {
+ while ((len = stream.read(buf)) > 0) {
+ outputStream.write(buf, 0, len);
+ currentProgress += len;
+ lastUpdateTime = System.currentTimeMillis();
+ }
+ } catch (IOException e) {
+ task.onError.handle(new ErrorEvent(e, task));
+ }
+ complete = true;
+ }
+}
diff --git a/src/main/java/com/ilummc/eagletdl/SplitDownload.java b/src/main/java/com/ilummc/eagletdl/SplitDownload.java
new file mode 100644
index 0000000..6727537
--- /dev/null
+++ b/src/main/java/com/ilummc/eagletdl/SplitDownload.java
@@ -0,0 +1,89 @@
+package com.ilummc.eagletdl;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.RandomAccessFile;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+class SplitDownload implements Runnable {
+
+ private URL url;
+ long startIndex, endIndex;
+ private File target;
+ private EagletTask task;
+
+ private transient long currentIndex, lastUpdateTime = System.currentTimeMillis(), tmpStart;
+ private transient int retry = 0;
+ private transient boolean complete;
+
+ SplitDownload(URL url, long startIndex, long endIndex, File dest, EagletTask task) {
+ this.url = url;
+ tmpStart = this.startIndex = this.currentIndex = startIndex;
+ this.endIndex = endIndex;
+ target = dest;
+ this.task = task;
+ }
+
+ void setStartIndex(long index) {
+ this.tmpStart = index;
+ }
+
+ long getLastUpdateTime() {
+ return lastUpdateTime;
+ }
+
+ long getCurrentIndex() {
+ return currentIndex;
+ }
+
+ int getRetry() {
+ return retry;
+ }
+
+ boolean isComplete() {
+ return complete || currentIndex == endIndex + 1;
+ }
+
+ @Override
+ public void run() {
+ try {
+ complete = false;
+ currentIndex = tmpStart;
+ HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+ // set the connection properties
+ task.httpHeader.forEach(connection::addRequestProperty);
+ connection.setRequestMethod(task.requestMethod);
+ connection.setConnectTimeout(task.connectionTimeout);
+ connection.setReadTimeout(task.readTimeout);
+ // set the download range
+ connection.setRequestProperty("Range", "bytes=" + tmpStart + "-" + endIndex);
+ connection.connect();
+ // if response code not equals 206, it means that the server do not support multi thread downloading
+ if (connection.getResponseCode() == 206) {
+ RandomAccessFile file = new RandomAccessFile(target, "rwd");
+ file.seek(tmpStart);
+ byte[] buf = new byte[1024];
+ int len;
+ try (BufferedInputStream stream = new BufferedInputStream(connection.getInputStream())) {
+ while ((len = stream.read(buf)) > 0) {
+ file.write(buf, 0, len);
+ lastUpdateTime = System.currentTimeMillis();
+ currentIndex += len;
+ // some mysterious error occurred while downloading
+ if (currentIndex >= endIndex + 2) {
+ currentIndex = tmpStart;
+ lastUpdateTime = 0;
+ retry++;
+ return;
+ }
+ }
+ complete = true;
+ }
+ } else throw new DoNotSupportMultipleThreadException();
+ } catch (Exception e) {
+ task.onError.handle(new ErrorEvent(e, task));
+ retry++;
+ }
+ }
+}
diff --git a/src/main/java/com/ilummc/eagletdl/StartEvent.java b/src/main/java/com/ilummc/eagletdl/StartEvent.java
new file mode 100644
index 0000000..b0c7f61
--- /dev/null
+++ b/src/main/java/com/ilummc/eagletdl/StartEvent.java
@@ -0,0 +1,14 @@
+package com.ilummc.eagletdl;
+
+public class StartEvent {
+
+ private EagletTask task;
+
+ StartEvent(EagletTask task) {
+ this.task = task;
+ }
+
+ public EagletTask getTask() {
+ return task;
+ }
+}