package zlc.season.rxdownload2.function; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.RandomAccessFile; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.text.ParseException; import io.reactivex.FlowableEmitter; import okhttp3.ResponseBody; import retrofit2.Response; import zlc.season.rxdownload2.entity.DownloadRange; import zlc.season.rxdownload2.entity.DownloadStatus; import static java.nio.channels.FileChannel.MapMode.READ_WRITE; import static zlc.season.rxdownload2.function.Constant.CHUNKED_DOWNLOAD_HINT; import static zlc.season.rxdownload2.function.Utils.GMTToLong; import static zlc.season.rxdownload2.function.Utils.closeQuietly; import static zlc.season.rxdownload2.function.Utils.isChunked; import static zlc.season.rxdownload2.function.Utils.log; import static zlc.season.rxdownload2.function.Utils.longToGMT; /** * Author: Season(ssseasonnn@gmail.com) * Date: 2016/11/17 * Time: 10:35 * <p> * File Helper */ public class FileHelper { private static final int EACH_RECORD_SIZE = 16; //long + long = 8 + 8 private static final String ACCESS = "rw"; private int RECORD_FILE_TOTAL_SIZE; //|*********************| //|*****Record File****| //|*********************| //| 0L | 7L | 0 //| 8L | 15L | 1 //| 16L | 31L | 2 //| ... | ... | maxThreads-1 //|*********************| private int maxThreads; public FileHelper(int maxThreads) { this.maxThreads = maxThreads; RECORD_FILE_TOTAL_SIZE = EACH_RECORD_SIZE * maxThreads; } public void prepareDownload(File lastModifyFile, File saveFile, long fileLength, String lastModify) throws IOException, ParseException { writeLastModify(lastModifyFile, lastModify); prepareFile(saveFile, fileLength); } public void saveFile(FlowableEmitter<DownloadStatus> emitter, File saveFile, Response<ResponseBody> resp) { InputStream inputStream = null; OutputStream outputStream = null; try { try { int readLen; int downloadSize = 0; byte[] buffer = new byte[8192]; DownloadStatus status = new DownloadStatus(); inputStream = resp.body().byteStream(); outputStream = new FileOutputStream(saveFile); long contentLength = resp.body().contentLength(); boolean isChunked = isChunked(resp); if (isChunked || contentLength == -1) { status.isChunked = true; } status.setTotalSize(contentLength); while ((readLen = inputStream.read(buffer)) != -1 && !emitter.isCancelled()) { outputStream.write(buffer, 0, readLen); downloadSize += readLen; status.setDownloadSize(downloadSize); emitter.onNext(status); } outputStream.flush(); // This is important!!! emitter.onComplete(); } finally { closeQuietly(inputStream); closeQuietly(outputStream); closeQuietly(resp.body()); } } catch (IOException e) { emitter.onError(e); } } public void prepareDownload(File lastModifyFile, File tempFile, File saveFile, long fileLength, String lastModify) throws IOException, ParseException { writeLastModify(lastModifyFile, lastModify); prepareFile(tempFile, saveFile, fileLength); } public void saveFile(FlowableEmitter<DownloadStatus> emitter, int i, File tempFile, File saveFile, ResponseBody response) { RandomAccessFile record = null; FileChannel recordChannel = null; RandomAccessFile save = null; FileChannel saveChannel = null; InputStream inStream = null; try { try { int readLen; byte[] buffer = new byte[2048]; DownloadStatus status = new DownloadStatus(); record = new RandomAccessFile(tempFile, ACCESS); recordChannel = record.getChannel(); MappedByteBuffer recordBuffer = recordChannel .map(READ_WRITE, 0, RECORD_FILE_TOTAL_SIZE); int startIndex = i * EACH_RECORD_SIZE; long start = recordBuffer.getLong(startIndex); // long end = recordBuffer.getLong(startIndex + 8); long totalSize = recordBuffer.getLong(RECORD_FILE_TOTAL_SIZE - 8) + 1; status.setTotalSize(totalSize); save = new RandomAccessFile(saveFile, ACCESS); saveChannel = save.getChannel(); inStream = response.byteStream(); while ((readLen = inStream.read(buffer)) != -1 && !emitter.isCancelled()) { MappedByteBuffer saveBuffer = saveChannel.map(READ_WRITE, start, readLen); start += readLen; saveBuffer.put(buffer, 0, readLen); recordBuffer.putLong(startIndex, start); status.setDownloadSize(totalSize - getResidue(recordBuffer)); emitter.onNext(status); } emitter.onComplete(); } finally { closeQuietly(record); closeQuietly(recordChannel); closeQuietly(save); closeQuietly(saveChannel); closeQuietly(inStream); closeQuietly(response); } } catch (IOException e) { emitter.onError(e); } } public boolean fileNotComplete(File tempFile) throws IOException { RandomAccessFile record = null; FileChannel channel = null; try { record = new RandomAccessFile(tempFile, ACCESS); channel = record.getChannel(); MappedByteBuffer buffer = channel.map(READ_WRITE, 0, RECORD_FILE_TOTAL_SIZE); long startByte; long endByte; for (int i = 0; i < maxThreads; i++) { startByte = buffer.getLong(); endByte = buffer.getLong(); if (startByte <= endByte) { return true; } } return false; } finally { closeQuietly(channel); closeQuietly(record); } } public boolean tempFileDamaged(File tempFile, long fileLength) throws IOException { RandomAccessFile record = null; FileChannel channel = null; try { record = new RandomAccessFile(tempFile, ACCESS); channel = record.getChannel(); MappedByteBuffer buffer = channel.map(READ_WRITE, 0, RECORD_FILE_TOTAL_SIZE); long recordTotalSize = buffer.getLong(RECORD_FILE_TOTAL_SIZE - 8) + 1; return recordTotalSize != fileLength; } finally { closeQuietly(channel); closeQuietly(record); } } public DownloadRange readDownloadRange(File tempFile, int i) throws IOException { RandomAccessFile record = null; FileChannel channel = null; try { record = new RandomAccessFile(tempFile, ACCESS); channel = record.getChannel(); MappedByteBuffer buffer = channel .map(READ_WRITE, i * EACH_RECORD_SIZE, (i + 1) * EACH_RECORD_SIZE); long startByte = buffer.getLong(); long endByte = buffer.getLong(); return new DownloadRange(startByte, endByte); } finally { closeQuietly(channel); closeQuietly(record); } } public String readLastModify(File lastModifyFile) throws IOException { RandomAccessFile record = null; try { record = new RandomAccessFile(lastModifyFile, ACCESS); record.seek(0); return longToGMT(record.readLong()); } finally { closeQuietly(record); } } private void prepareFile(File tempFile, File saveFile, long fileLength) throws IOException { RandomAccessFile rFile = null; RandomAccessFile rRecord = null; FileChannel channel = null; try { rFile = new RandomAccessFile(saveFile, ACCESS); rFile.setLength(fileLength);//设置下载文件的长度 rRecord = new RandomAccessFile(tempFile, ACCESS); rRecord.setLength(RECORD_FILE_TOTAL_SIZE); //设置指针记录文件的大小 channel = rRecord.getChannel(); MappedByteBuffer buffer = channel.map(READ_WRITE, 0, RECORD_FILE_TOTAL_SIZE); long start; long end; int eachSize = (int) (fileLength / maxThreads); for (int i = 0; i < maxThreads; i++) { if (i == maxThreads - 1) { start = i * eachSize; end = fileLength - 1; } else { start = i * eachSize; end = (i + 1) * eachSize - 1; } buffer.putLong(start); buffer.putLong(end); } } finally { closeQuietly(channel); closeQuietly(rRecord); closeQuietly(rFile); } } private void prepareFile(File saveFile, long fileLength) throws IOException { RandomAccessFile file = null; try { file = new RandomAccessFile(saveFile, ACCESS); if (fileLength != -1) { file.setLength(fileLength);//设置下载文件的长度 } else { log(CHUNKED_DOWNLOAD_HINT); //Chunked 下载, 无需设置文件大小. } } finally { closeQuietly(file); } } private void writeLastModify(File file, String lastModify) throws IOException, ParseException { RandomAccessFile record = null; try { record = new RandomAccessFile(file, ACCESS); record.setLength(8); record.seek(0); record.writeLong(GMTToLong(lastModify)); } finally { closeQuietly(record); } } /** * 还剩多少字节没有下载 * * @param recordBuffer buffer * @return 剩余的字节 */ private long getResidue(MappedByteBuffer recordBuffer) { long residue = 0; for (int j = 0; j < maxThreads; j++) { long startTemp = recordBuffer.getLong(j * EACH_RECORD_SIZE); long endTemp = recordBuffer.getLong(j * EACH_RECORD_SIZE + 8); long temp = endTemp - startTemp + 1; residue += temp; } return residue; } }