diff --git a/okhttps-test/src/main/java/com/ejlchina/okhttps/test/eric/Download2.java b/okhttps-test/src/main/java/com/ejlchina/okhttps/test/eric/Download2.java new file mode 100644 index 0000000000000000000000000000000000000000..3c17f70eebec521cfa7a586a5a970c2117e7fa38 --- /dev/null +++ b/okhttps-test/src/main/java/com/ejlchina/okhttps/test/eric/Download2.java @@ -0,0 +1,60 @@ +package com.ejlchina.okhttps.test.eric; + +import com.ejlchina.okhttps.OkHttps; + +import java.io.File; + +import static java.lang.Thread.sleep; + +/** + * @author eric + * @program okhttps-parent + * @description + * @date 2020/12/24 + */ +public class Download2 { + static String url = "https://typora.io/windows/typora-setup-x64.exe"; + + public static void main(String[] args) { + System.out.println("程序开始了"); + + long totalSize = OkHttps.sync(url).get() +// .getBody() + .close() // 因为这次请求只是为了获得文件大小,不消费报文体,所以直接关闭 + .getContentLength(); // 获得待下载文件的大小(由于未消费报文体,所以该请求不会消耗下载报文体的时间和网络流量) + download(totalSize, 0); // 从第 0 块开始下载 + System.out.println("download跑完了"); + System.out.println("线程开始沉睡"); + + try { + sleep(50000); // 等待下载完成(不然本例的主线程就结束啦) + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.out.println("程序跑完了"); + + } + + + + static void download(long totalSize, int index) { + System.out.println("===>" + index); + + long size = 3 * 1024 * 1024; // 每块下载 3M + long start = index * size; + long end = Math.min(start + size, totalSize); + OkHttps.sync(url) + .setRange(start, end) // 设置本次下载的范围 + .get().getBody() + .toFile("D:/download/typora-setup-x64-5.exe") // 下载到同一个文件里 + .setAppended() // 开启文件追加模式 + .setOnSuccess((File file) -> { + if (end < totalSize) { // 若未下载完,则继续下载下一块 + download(totalSize, index + 1); + } else { + System.out.println("下载完成"); + } + }) + .start(); + } +} diff --git a/okhttps-test/src/main/java/com/ejlchina/okhttps/test/eric/OkhttpsDownload.java b/okhttps-test/src/main/java/com/ejlchina/okhttps/test/eric/OkhttpsDownload.java new file mode 100644 index 0000000000000000000000000000000000000000..5c56968fd722c8dc939d2e519a77ce845b757c37 --- /dev/null +++ b/okhttps-test/src/main/java/com/ejlchina/okhttps/test/eric/OkhttpsDownload.java @@ -0,0 +1,61 @@ +package com.ejlchina.okhttps.test.eric; + +import com.ejlchina.okhttps.OkHttps; + + +import java.io.File; + +import static java.lang.Thread.sleep; + +/** + * @author eric + * @program bytecode + * @description 下载 + * @date 2020/12/23 + */ +public class OkhttpsDownload { + + static String url = "https://typora.io/windows/typora-setup-x64.exe"; + + public static void main(String[] args) { + System.out.println("程序开始了"); + + long totalSize = OkHttps.sync(url).get() +// .getBody() + .close() // 因为这次请求只是为了获得文件大小,不消费报文体,所以直接关闭 + .getContentLength(); // 获得待下载文件的大小(由于未消费报文体,所以该请求不会消耗下载报文体的时间和网络流量) + + download(totalSize, 0); // 从第 0 块开始下载 + System.out.println("download跑完了"); + System.out.println("线程开始沉睡"); + + try { + sleep(50000); // 等待下载完成(不然本例的主线程就结束啦) + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.out.println("程序跑完了"); + + } + + static void download(long totalSize, int index) { + System.out.println("===>" + index); + + long size = 3 * 1024 * 1024; // 每块下载 3M + long start = index * size; + long end = Math.min(start + size, totalSize); + OkHttps.sync(url) + .setRange(start, end) // 设置本次下载的范围 + .get().getBody() + .toFile("D:/download/typora-setup-x64-2.exe") // 下载到同一个文件里 + .setAppended() // 开启文件追加模式 + .setOnSuccess((File file) -> { + if (end < totalSize) { // 若未下载完,则继续下载下一块 + download(totalSize, index + 1); + } else { + System.out.println("下载完成"); + } + }) + .start(); + } +} diff --git a/okhttps/src/main/java/com/ejlchina/okhttps/internal/ResultBody.java b/okhttps/src/main/java/com/ejlchina/okhttps/internal/ResultBody.java index 0d58bfe08194552c48c5500c0a27fecd668245ab..d24c2fc80141c7b259e1c12f1f0c14655adc1230 100644 --- a/okhttps/src/main/java/com/ejlchina/okhttps/internal/ResultBody.java +++ b/okhttps/src/main/java/com/ejlchina/okhttps/internal/ResultBody.java @@ -17,304 +17,305 @@ import okio.Buffer; import okio.ByteString; public class ResultBody extends AbstractBody implements Body { - - private final Response response; - private boolean onIO = false; - private OnCallback onProcess; - private long stepBytes = 0; - private double stepRate = -1; - private boolean rangeIgnored = false; - private HttpTask httpTask; - private boolean cached = false; - private byte[] data; - - ResultBody(HttpTask httpTask, Response response, TaskExecutor taskExecutor) { - super(taskExecutor, httpTask.charset(response)); - this.httpTask = httpTask; - this.response = response; - } - - - @Override - public MediaType getType() { - ResponseBody body = response.body(); - if (body != null) { - return body.contentType(); - } - return null; - } - - @Override - public long getLength() { - ResponseBody body = response.body(); - if (body != null) { - return body.contentLength(); - } - return 0; - } - - @Override + + private final Response response; + private boolean onIO = false; + private OnCallback onProcess; + private long stepBytes = 0; + private double stepRate = -1; + private boolean rangeIgnored = false; + private HttpTask httpTask; + private boolean cached = false; + private byte[] data; + + ResultBody(HttpTask httpTask, Response response, TaskExecutor taskExecutor) { + super(taskExecutor, httpTask.charset(response)); + this.httpTask = httpTask; + this.response = response; + } + + + @Override + public MediaType getType() { + ResponseBody body = response.body(); + if (body != null) { + return body.contentType(); + } + return null; + } + + @Override + public long getLength() { + ResponseBody body = response.body(); + if (body != null) { + return body.contentLength(); + } + return 0; + } + + @Override public Body nextOnIO() { - onIO = true; - return this; + onIO = true; + return this; + } + + @Override + public Body setOnProcess(OnCallback onProcess) { + if (taskExecutor == null) { + throw new IllegalStateException("没有 taskExecutor,不可设置下载进度回调!"); + } + if (cached) { + throw new IllegalStateException("开启缓存后,不可设置下载进度回调!"); + } + this.onProcess = onProcess; + return this; + } + + @Override + public Body stepBytes(long stepBytes) { + this.stepBytes = stepBytes; + return this; + } + + @Override + public Body stepRate(double stepRate) { + this.stepRate = stepRate; + return this; + } + + @Override + public Body setRangeIgnored() { + this.rangeIgnored = true; + return this; + } + + @Override + public InputStream toByteStream() { + InputStream input; + if (cached) { + input = new ByteArrayInputStream(cacheBytes()); + } else { + ResponseBody body = response.body(); + if (body != null) { + input = body.byteStream(); + } else { + input = new ByteArrayInputStream(new byte[0]); + } + } + if (onProcess != null) { + long rangeStart = getRangeStart(); + long totalBytes = getLength(); + if (!rangeIgnored) { + totalBytes += rangeStart; + } + if (stepRate > 0 && stepRate <= 1) { + stepBytes = (long) (totalBytes * stepRate); + } + if (stepBytes <= 0) { + stepBytes = Process.DEFAULT_STEP_BYTES; + } + return new ProcessInputStream(input, onProcess, totalBytes, stepBytes, + rangeIgnored ? 0 : rangeStart, taskExecutor.getExecutor(onIO)); + } + return input; + } + + @Override + public byte[] toBytes() { + if (cached) { + return cacheBytes(); + } + return bodyToBytes(); + } + + @Override + public Reader toCharStream() { + if (cached || onProcess != null) { + return new InputStreamReader(toByteStream()); + } + ResponseBody body = response.body(); + if (body != null) { + return body.charStream(); + } + return new CharArrayReader(new char[]{}); + } + + @Override + public String toString() { + if (cached || onProcess != null) { + return new String(toBytes(), charset); + } + try { + ResponseBody body = response.body(); + if (body != null) { + return new String(body.bytes(), charset); + } + } catch (IOException e) { + throw new HttpException("报文体转化字符串出错", e); + } + return null; + } + + @Override + public ByteString toByteString() { + return ByteString.of(toBytes()); } - @Override - public Body setOnProcess(OnCallback onProcess) { - if (taskExecutor == null) { - throw new IllegalStateException("没有 taskExecutor,不可设置下载进度回调!"); - } - if (cached) { - throw new IllegalStateException("开启缓存后,不可设置下载进度回调!"); - } - this.onProcess = onProcess; - return this; - } - - @Override - public Body stepBytes(long stepBytes) { - this.stepBytes = stepBytes; - return this; - } - - @Override - public Body stepRate(double stepRate) { - this.stepRate = stepRate; - return this; - } - - @Override - public Body setRangeIgnored() { - this.rangeIgnored =true; - return this; - } - - @Override - public InputStream toByteStream() { - InputStream input; - if (cached) { - input = new ByteArrayInputStream(cacheBytes()); - } else { - ResponseBody body = response.body(); - if (body != null) { - input = body.byteStream(); - } else { - input = new ByteArrayInputStream(new byte[0]); - } - } - if (onProcess != null) { - long rangeStart = getRangeStart(); - long totalBytes = getLength(); - if (!rangeIgnored) { - totalBytes += rangeStart; - } - if (stepRate > 0 && stepRate <= 1) { - stepBytes = (long) (totalBytes * stepRate); - } - if (stepBytes <= 0) { - stepBytes = Process.DEFAULT_STEP_BYTES; - } - return new ProcessInputStream(input, onProcess, totalBytes, stepBytes, - rangeIgnored ? 0 : rangeStart, taskExecutor.getExecutor(onIO)); - } - return input; - } - - @Override - public byte[] toBytes() { - if (cached) { - return cacheBytes(); - } - return bodyToBytes(); - } - - @Override - public Reader toCharStream() { - if (cached || onProcess != null) { - return new InputStreamReader(toByteStream()); - } - ResponseBody body = response.body(); - if (body != null) { - return body.charStream(); - } - return new CharArrayReader(new char[]{}); - } - - @Override - public String toString() { - if (cached || onProcess != null) { - return new String(toBytes(), charset); - } - try { - ResponseBody body = response.body(); - if (body != null) { - return new String(body.bytes(), charset); - } - } catch (IOException e) { - throw new HttpException("报文体转化字符串出错", e); - } - return null; - } - - @Override - public ByteString toByteString() { - return ByteString.of(toBytes()); - } - - @Override - public Download toFile(String filePath) { - return toFile(new File(filePath)); - } - - @Override - public Download toFile(File file) { - if (taskExecutor == null) { - throw new IllegalStateException("没有 taskExecutor, 不可进行下载操作!"); - } - if (!file.exists()) { - try { - File parent = file.getParentFile(); - if (!parent.exists()) { - parent.mkdirs(); - } - file.createNewFile(); - } catch (IOException e) { - response.close(); - throw new HttpException( - "Cannot create file [" + file.getAbsolutePath() + "]", e); - } - } - return taskExecutor.download(httpTask, file, toByteStream(), - getRangeStart()); - } - - @Override - public Download toFolder(String dirPath) { - String fileName = resolveFileName(); - String filePath = resolveFilePath(dirPath, fileName); - int index = 0; - File file = new File(filePath); - while (file.exists()) { - String indexFileName = indexFileName(fileName, index++); - filePath = resolveFilePath(dirPath, indexFileName); - file = new File(filePath); - } - return toFile(file); - } - - @Override - public Download toFolder(File dir) { - if (dir.exists() && !dir.isDirectory()) { - response.close(); - throw new HttpException("文件下载失败:文件[" + dir.getAbsolutePath() + "]已存在,并且不是一个目录!"); - } - if (!dir.exists()) { - dir.mkdirs(); - } - return toFolder(dir.getAbsolutePath()); - } - - @Override - public Body cache() { - if (onProcess != null) { - throw new IllegalStateException("设置了下载进度回调,不可再开启缓存!"); - } - cached = true; - return this; - } - - @Override - public Body close() { - response.close(); - data = null; - return this; - } - - private byte[] cacheBytes() { - synchronized (response) { - if (data == null) { - data = bodyToBytes(); - } - } - return data; - } - - private byte[] bodyToBytes() { - if (onProcess != null) { - try (Buffer buffer = new Buffer()) { - return buffer.readFrom(toByteStream()).readByteArray(); - } catch (IOException e) { - throw new HttpException("报文体转化字节数组出错", e); - } finally { - response.close(); - } - } - ResponseBody body = response.body(); - if (body != null) { - try { - return body.bytes(); - } catch (IOException e) { - throw new HttpException("报文体转化字节数组出错", e); - } - } - return new byte[0]; - } - - private long getRangeStart() { - long rangeStart = 0; - if (response.code() != HttpURLConnection.HTTP_PARTIAL) { - return rangeStart; - } - String range = response.header("Content-Range"); - if (range != null && range.startsWith("bytes")) { - int index = range.indexOf('-'); - if (index > 5) { - String start = range.substring(5, index).trim(); - try { - rangeStart = Long.parseLong(start); - } catch (Exception ignore) {} - } - } - return rangeStart; - } - - private String resolveFilePath(String dirPath, String fileName) { - if (dirPath.endsWith("\\") || dirPath.endsWith("/")) { - return dirPath + fileName; - } - return dirPath + "\\" + fileName; - } - - private String indexFileName(String fileName, int index) { - int i = fileName.lastIndexOf('.'); - if (i < 0) { - return fileName + "(" + index + ")"; - } - String ext = fileName.substring(i); - if (i > 0) { - String name = fileName.substring(0, i); - return name + "(" + index + ")" + ext; - } - return "(" + index + ")" + ext; - } - - private String resolveFileName() { - String fileName = response.header("Content-Disposition"); + @Override + public Download toFile(String filePath) { + return toFile(new File(filePath)); + } + + @Override + public Download toFile(File file) { + if (taskExecutor == null) { + throw new IllegalStateException("没有 taskExecutor, 不可进行下载操作!"); + } + if (!file.exists()) { + try { + File parent = file.getParentFile(); + if (parent != null && !parent.exists()) { + parent.mkdirs(); + } + file.createNewFile(); + } catch (IOException e) { + response.close(); + throw new HttpException( + "Cannot create file [" + file.getAbsolutePath() + "]", e); + } + } + return taskExecutor.download(httpTask, file, toByteStream(), + getRangeStart()); + } + + @Override + public Download toFolder(String dirPath) { + String fileName = resolveFileName(); + String filePath = resolveFilePath(dirPath, fileName); + int index = 0; + File file = new File(filePath); + while (file.exists()) { + String indexFileName = indexFileName(fileName, index++); + filePath = resolveFilePath(dirPath, indexFileName); + file = new File(filePath); + } + return toFile(file); + } + + @Override + public Download toFolder(File dir) { + if (dir.exists() && !dir.isDirectory()) { + response.close(); + throw new HttpException("文件下载失败:文件[" + dir.getAbsolutePath() + "]已存在,并且不是一个目录!"); + } + if (!dir.exists()) { + dir.mkdirs(); + } + return toFolder(dir.getAbsolutePath()); + } + + @Override + public Body cache() { + if (onProcess != null) { + throw new IllegalStateException("设置了下载进度回调,不可再开启缓存!"); + } + cached = true; + return this; + } + + @Override + public Body close() { + response.close(); + data = null; + return this; + } + + private byte[] cacheBytes() { + synchronized (response) { + if (data == null) { + data = bodyToBytes(); + } + } + return data; + } + + private byte[] bodyToBytes() { + if (onProcess != null) { + try (Buffer buffer = new Buffer()) { + return buffer.readFrom(toByteStream()).readByteArray(); + } catch (IOException e) { + throw new HttpException("报文体转化字节数组出错", e); + } finally { + response.close(); + } + } + ResponseBody body = response.body(); + if (body != null) { + try { + return body.bytes(); + } catch (IOException e) { + throw new HttpException("报文体转化字节数组出错", e); + } + } + return new byte[0]; + } + + private long getRangeStart() { + long rangeStart = 0; + if (response.code() != HttpURLConnection.HTTP_PARTIAL) { + return rangeStart; + } + String range = response.header("Content-Range"); + if (range != null && range.startsWith("bytes")) { + int index = range.indexOf('-'); + if (index > 5) { + String start = range.substring(5, index).trim(); + try { + rangeStart = Long.parseLong(start); + } catch (Exception ignore) { + } + } + } + return rangeStart; + } + + private String resolveFilePath(String dirPath, String fileName) { + if (dirPath.endsWith("\\") || dirPath.endsWith("/")) { + return dirPath + fileName; + } + return dirPath + "\\" + fileName; + } + + private String indexFileName(String fileName, int index) { + int i = fileName.lastIndexOf('.'); + if (i < 0) { + return fileName + "(" + index + ")"; + } + String ext = fileName.substring(i); + if (i > 0) { + String name = fileName.substring(0, i); + return name + "(" + index + ")" + ext; + } + return "(" + index + ")" + ext; + } + + private String resolveFileName() { + String fileName = response.header("Content-Disposition"); // 通过Content-Disposition获取文件名,这点跟服务器有关,需要灵活变通 if (fileName == null || fileName.length() < 1) { - fileName = response.request().url().encodedPath(); + fileName = response.request().url().encodedPath(); fileName = fileName.substring(fileName.lastIndexOf("/") + 1); } else { try { - fileName = URLDecoder.decode(fileName.substring( - fileName.indexOf("filename=") + 9), "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new HttpException("解码文件名失败", e); - } + fileName = URLDecoder.decode(fileName.substring( + fileName.indexOf("filename=") + 9), "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new HttpException("解码文件名失败", e); + } // 有些文件名会被包含在""里面,所以要去掉,不然无法读取文件后缀 fileName = fileName.replaceAll("\"", ""); } return fileName; - } + } }