diff --git a/README.md b/README.md index d44d9e7..e5dc7e5 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,10 @@ Java File Downloader, download File using HttpURLConnection of Java ### Features -1. If File already partially downloaded, download resume from there. -2. Provide CLI interface for Download File -3. CLI display progressbar on console for monitor downloading process. +1. Download file from HttpUrl +2. If File already partially downloaded, download resume from there. +3. Provide CLI interface for Download File +4. CLI display progressbar on console for monitor downloading process. ### How to use 1. Execute **gradle** command from home directory @@ -37,4 +38,3 @@ Please enter Download File URL:http://speedtest.ftp.otenet.gr/files/test100k.db Please enter File Location : /tmp 100 % [...................................................] ``` - diff --git a/src/main/java/com/arkaya/filedownloader/cli/FileDownloadCLIExecutor.java b/src/main/java/com/arkaya/filedownloader/cli/FileDownloadCLIExecutor.java index 00d99b2..4289c5b 100644 --- a/src/main/java/com/arkaya/filedownloader/cli/FileDownloadCLIExecutor.java +++ b/src/main/java/com/arkaya/filedownloader/cli/FileDownloadCLIExecutor.java @@ -2,6 +2,7 @@ import com.arkaya.filedownloader.constant.FileDownloadStatus; import com.arkaya.filedownloader.download.constant.FileDownloadKeyConstant; +import com.arkaya.filedownloader.download.task.FileDownloadInfo; import com.arkaya.filedownloader.download.task.FileDownloadTaskHandler; import com.arkaya.filedownloader.request.http.HttpFileDownloadRequest; import com.arkaya.filedownloader.response.FileDownloadResponse; @@ -11,9 +12,12 @@ import org.springframework.stereotype.Component; import org.springframework.util.ResourceUtils; +import javax.annotation.PreDestroy; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Scanner; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; @Component public class FileDownloadCLIExecutor { @@ -24,6 +28,17 @@ public class FileDownloadCLIExecutor { @Autowired private FileDownloadTaskHandler fileDownloadTaskHandler; + private final ExecutorService executorService; + + public FileDownloadCLIExecutor() { + this.executorService = Executors.newSingleThreadExecutor(); + } + + @PreDestroy + public void destroy() { + this.executorService.shutdown(); + } + public void execute() { Scanner scanner = new Scanner(System.in); System.out.print("Please enter Download File URL:"); @@ -53,9 +68,13 @@ public void execute() { } private void startProgressBar(HttpFileDownloadRequest httpFileDownloadRequest) { + FileDownloadProgressbar fileDownloadProgressbar = new FileDownloadProgressbar(); String partialFileName = httpFileDownloadRequest.getAdditionalProperty(FileDownloadKeyConstant.PARTIAL_FILE_NAME); fileDownloadTaskHandler.getFileDownloadTask(partialFileName) - .ifPresent(fileDownloadInfo -> new FileDownloadProgressbar(fileDownloadInfo).startProgressBar()); + .ifPresent((FileDownloadInfo fileDownloadInfo) -> { + fileDownloadInfo.registerFileDownloadListener(fileDownloadProgressbar); + executorService.submit(fileDownloadProgressbar); + }); } private boolean isValidFileLocation(String fileLocation) { diff --git a/src/main/java/com/arkaya/filedownloader/cli/FileDownloadProgressListener.java b/src/main/java/com/arkaya/filedownloader/cli/FileDownloadProgressListener.java deleted file mode 100644 index 09836a2..0000000 --- a/src/main/java/com/arkaya/filedownloader/cli/FileDownloadProgressListener.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.arkaya.filedownloader.cli; - -import com.arkaya.filedownloader.download.task.FileDownloadInfo; - -/** - * Created by punit.patel on 1/9/17. - */ -public class FileDownloadProgressListener { - private int fileSize; - private FileDownloadInfo fileDownloadInfo; -} diff --git a/src/main/java/com/arkaya/filedownloader/cli/FileDownloadProgressbar.java b/src/main/java/com/arkaya/filedownloader/cli/FileDownloadProgressbar.java index 1253e79..5b54d4c 100644 --- a/src/main/java/com/arkaya/filedownloader/cli/FileDownloadProgressbar.java +++ b/src/main/java/com/arkaya/filedownloader/cli/FileDownloadProgressbar.java @@ -1,68 +1,70 @@ package com.arkaya.filedownloader.cli; import com.arkaya.filedownloader.constant.FileDownloadStatus; -import com.arkaya.filedownloader.download.constant.FileDownloadKeyConstant; -import com.arkaya.filedownloader.download.task.FileDownloadInfo; -import com.arkaya.filedownloader.request.http.HttpFileDownloadRequest; -import org.apache.commons.lang3.math.NumberUtils; +import com.arkaya.filedownloader.download.FileDownloadEvent; +import com.arkaya.filedownloader.download.FileDownloadListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; +import java.util.concurrent.Callable; -public class FileDownloadProgressbar { +public class FileDownloadProgressbar implements FileDownloadListener, Callable { private final static Logger LOGGER = LoggerFactory.getLogger(FileDownloadProgressbar.class); - private FileDownloadInfo fileDownloadInfo; - private final Path partialFileName; - private final long fileSize; + private FileDownloadStatus fileDownloadStatus; + private long fileSize; + private long partialFileSize; - public FileDownloadProgressbar(FileDownloadInfo fileDownloadInfo) { - HttpFileDownloadRequest fileDownloadRequest = fileDownloadInfo.getFileDownloadRequest(); - this.partialFileName = Paths.get(fileDownloadRequest.getAdditionalProperty(FileDownloadKeyConstant.PARTIAL_FILE_NAME)); - this.fileSize = NumberUtils.toLong(fileDownloadRequest.getAdditionalProperty(FileDownloadKeyConstant.FILE_SIZE)); - this.fileDownloadInfo = fileDownloadInfo; + public FileDownloadProgressbar() { + this.fileDownloadStatus = FileDownloadStatus.INITIATE; } - public void startProgressBar() { + @Override + public void onDownloadingFile(FileDownloadEvent fileDownloadEvent) { + this.fileDownloadStatus = fileDownloadEvent.getFileDownloadStatus(); + this.fileSize = fileDownloadEvent.getFileSize(); + this.partialFileSize = fileDownloadEvent.getPartialFileSize(); + } + @Override + public FileDownloadStatus call() throws Exception { try { + updateProgress(0); while (true) { - - if (fileDownloadInfo.getFileDownloadStatus() == FileDownloadStatus.COMPLETE) { - updateProgress(100); - break; - } - - while (fileDownloadInfo.getFileDownloadStatus() == FileDownloadStatus.DOWNLOADING) { - updateProgress(calculateProcessPercentage()); - Thread.sleep(500); - } - - if (fileDownloadInfo.getFileDownloadStatus() == FileDownloadStatus.FAIL) { - System.err.println("File Downloading Fail, Please contact system admin"); - break; + Thread.sleep(500); + switch (fileDownloadStatus) { + case INITIATE: + break; + case DOWNLOADING: + updateProgress(calculateProcessPercentage()); + break; + case COMPLETE: + updateProgress(calculateProcessPercentage()); + return fileDownloadStatus; + case FAIL: + System.err.println("File Downloading Fail, Please contact system admin"); + return fileDownloadStatus; } } } catch (Exception e) { - System.err.println("Error while downloading file, reason " + e.getMessage()); + LOGGER.error("Error while downloading file", e); e.printStackTrace(); + System.err.println("Error while downloading file, Reason :" + e.getMessage()); } + return FileDownloadStatus.FAIL; } private int calculateProcessPercentage() throws IOException { - long partialFileSize = Files.size(partialFileName); LOGGER.debug("calculate percentage for partial file size {} and file size {}", partialFileSize, fileSize); - return (int) Math.floor(100 * partialFileSize/fileSize); + return (int) Math.floor(100 * partialFileSize / fileSize); } + static void updateProgress(int progressPercentage) { StringBuilder builder = new StringBuilder("\r"); - builder.append(progressPercentage +" % ["); + builder.append(progressPercentage + " % ["); int i = 0; - for (; i <= progressPercentage/2; i++) { + for (; i <= progressPercentage / 2; i++) { builder.append("."); } for (; i < 50; i++) { diff --git a/src/main/java/com/arkaya/filedownloader/download/FileDownloadEvent.java b/src/main/java/com/arkaya/filedownloader/download/FileDownloadEvent.java new file mode 100644 index 0000000..c8b0b30 --- /dev/null +++ b/src/main/java/com/arkaya/filedownloader/download/FileDownloadEvent.java @@ -0,0 +1,30 @@ +package com.arkaya.filedownloader.download; + +import com.arkaya.filedownloader.constant.FileDownloadStatus; + +/** + * Created by punit.patel on 1/9/17. + */ +public class FileDownloadEvent { + private long fileSize; + private long partialFileSize; + private FileDownloadStatus fileDownloadStatus; + + public FileDownloadEvent(FileDownloadStatus fileDownloadStatus, long fileSize, long partialFileSize) { + this.fileDownloadStatus = fileDownloadStatus; + this.fileSize = fileSize; + this.partialFileSize = partialFileSize; + } + + public long getFileSize() { + return fileSize; + } + + public long getPartialFileSize() { + return partialFileSize; + } + + public FileDownloadStatus getFileDownloadStatus() { + return fileDownloadStatus; + } +} diff --git a/src/main/java/com/arkaya/filedownloader/download/FileDownloadListener.java b/src/main/java/com/arkaya/filedownloader/download/FileDownloadListener.java new file mode 100644 index 0000000..0974293 --- /dev/null +++ b/src/main/java/com/arkaya/filedownloader/download/FileDownloadListener.java @@ -0,0 +1,5 @@ +package com.arkaya.filedownloader.download; + +public interface FileDownloadListener { + void onDownloadingFile(FileDownloadEvent fileDownloadEvent); +} diff --git a/src/main/java/com/arkaya/filedownloader/download/task/FileDownloadInfo.java b/src/main/java/com/arkaya/filedownloader/download/task/FileDownloadInfo.java index b40bfd3..f83cb24 100644 --- a/src/main/java/com/arkaya/filedownloader/download/task/FileDownloadInfo.java +++ b/src/main/java/com/arkaya/filedownloader/download/task/FileDownloadInfo.java @@ -1,6 +1,7 @@ package com.arkaya.filedownloader.download.task; import com.arkaya.filedownloader.constant.FileDownloadStatus; +import com.arkaya.filedownloader.download.FileDownloadListener; import com.arkaya.filedownloader.request.http.HttpFileDownloadRequest; public interface FileDownloadInfo { @@ -8,4 +9,5 @@ public interface FileDownloadInfo { FileDownloadStatus getFileDownloadStatus(); + void registerFileDownloadListener(FileDownloadListener fileDownloadListener); } diff --git a/src/main/java/com/arkaya/filedownloader/download/task/FileDownloadInfoDetail.java b/src/main/java/com/arkaya/filedownloader/download/task/FileDownloadInfoDetail.java index 3360540..2833e96 100644 --- a/src/main/java/com/arkaya/filedownloader/download/task/FileDownloadInfoDetail.java +++ b/src/main/java/com/arkaya/filedownloader/download/task/FileDownloadInfoDetail.java @@ -1,27 +1,43 @@ package com.arkaya.filedownloader.download.task; import com.arkaya.filedownloader.constant.FileDownloadStatus; +import com.arkaya.filedownloader.download.FileDownloadListener; import com.arkaya.filedownloader.request.http.HttpFileDownloadRequest; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + public class FileDownloadInfoDetail implements FileDownloadInfo { private HttpFileDownloadRequest fileDownloadRequest; private volatile FileDownloadStatus fileDownloadStatus; + private List fileDownloadListenerList; public FileDownloadInfoDetail(HttpFileDownloadRequest fileDownloadRequest) { this.fileDownloadRequest = fileDownloadRequest; this.fileDownloadStatus = FileDownloadStatus.INITIATE; + this.fileDownloadListenerList = new CopyOnWriteArrayList<>(); } + @Override public HttpFileDownloadRequest getFileDownloadRequest() { return fileDownloadRequest; } + @Override public FileDownloadStatus getFileDownloadStatus() { return fileDownloadStatus; } + @Override + public void registerFileDownloadListener(FileDownloadListener fileDownloadListener) { + fileDownloadListenerList.add(fileDownloadListener); + } + public void changeFileDownloadStatus(FileDownloadStatus fileDownloadStatus) { this.fileDownloadStatus = fileDownloadStatus; } + public List getFileDownloadListenerList() { + return fileDownloadListenerList; + } } diff --git a/src/main/java/com/arkaya/filedownloader/download/task/FileDownloadTask.java b/src/main/java/com/arkaya/filedownloader/download/task/FileDownloadTask.java index e30ffdf..9dce671 100644 --- a/src/main/java/com/arkaya/filedownloader/download/task/FileDownloadTask.java +++ b/src/main/java/com/arkaya/filedownloader/download/task/FileDownloadTask.java @@ -2,6 +2,7 @@ import com.arkaya.filedownloader.constant.FileDownloadStatus; +import com.arkaya.filedownloader.download.FileDownloadEvent; import com.arkaya.filedownloader.download.constant.FileDownloadKeyConstant; import com.arkaya.filedownloader.download.http.HttpURLConnectionUtil; import com.arkaya.filedownloader.download.util.FileDownloadUtil; @@ -17,12 +18,10 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; public class FileDownloadTask implements Runnable { private static final Logger LOGGER = LoggerFactory.getLogger(FileDownloadTask.class); - private static final int BUFFER_SIZE = 4896; + private static final int BUFFER_SIZE = 489600; private final String partialFileName; private final long fileSize; private final String url; @@ -52,6 +51,7 @@ public void run() { Files.move(partialFilePath, downloadFilePath); LOGGER.debug("File {} download completed", downloadFilePath); changeStatus(FileDownloadStatus.COMPLETE); + notifyListeners(new FileDownloadEvent(FileDownloadStatus.COMPLETE, fileSize, partFileSize)); break; } long startByteRange = partFileSize; @@ -61,10 +61,12 @@ public void run() { } partFileSize = endByteRange; downloadFile(startByteRange, endByteRange); + notifyListeners(new FileDownloadEvent(FileDownloadStatus.DOWNLOADING, fileSize, partFileSize)); } } catch (Exception e) { - LOGGER.error("Error while download File",e); + LOGGER.error("Error while download File", e); changeStatus(FileDownloadStatus.FAIL); + notifyListeners(new FileDownloadEvent(FileDownloadStatus.FAIL, fileSize, 0)); } } @@ -72,6 +74,10 @@ private void changeStatus(FileDownloadStatus fileDownloadStatus) { fileDownloadInfo.changeFileDownloadStatus(fileDownloadStatus); } + private void notifyListeners(FileDownloadEvent fileDownloadEvent) { + fileDownloadInfo.getFileDownloadListenerList().forEach(fileDownloadListener -> fileDownloadListener.onDownloadingFile(fileDownloadEvent)); + } + private void downloadFile(long startByteRange, long endByteRange) throws IOException { LOGGER.debug("Download file from {} to {}", startByteRange, endByteRange); HttpURLConnection connection = null; diff --git a/src/main/java/com/arkaya/filedownloader/download/task/FileDownloadTaskHandler.java b/src/main/java/com/arkaya/filedownloader/download/task/FileDownloadTaskHandler.java index 4d77091..d693dc3 100644 --- a/src/main/java/com/arkaya/filedownloader/download/task/FileDownloadTaskHandler.java +++ b/src/main/java/com/arkaya/filedownloader/download/task/FileDownloadTaskHandler.java @@ -48,9 +48,26 @@ public void startDownloading(HttpFileDownloadRequest httpFileDownloadRequest, Ht executorService.execute(fileDownloadTask); response.setFileDownloadStatus(FileDownloadStatus.INITIATE) .setResponseMessage(FileDownloadStatus.INITIATE.getMessage()); + postProcessor(httpFileDownloadRequest, fileDownloadInfoDetail); } + private void postProcessor(HttpFileDownloadRequest httpFileDownloadRequest, FileDownloadInfoDetail fileDownloadInfoDetail) { + fileDownloadInfoDetail.registerFileDownloadListener(fileDownloadEvent -> { + switch (fileDownloadEvent.getFileDownloadStatus()) { + case COMPLETE: + remove(httpFileDownloadRequest); + break; + case FAIL: + remove(httpFileDownloadRequest); + break; + } + }); + } + public FileDownloadInfo remove(HttpFileDownloadRequest httpFileDownloadRequest) { + String partialFileName = httpFileDownloadRequest.getAdditionalProperty(FileDownloadKeyConstant.PARTIAL_FILE_NAME); + return partialFileNameToTaskMap.remove(partialFileName); + } } diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 5970135..61dcbd2 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -2,8 +2,11 @@ - file-downloader.log + file-downloader.log true + + %d %m%n + diff --git a/src/test/java/com/arkaya/filedownloader/download/task/FileDownloadTaskTest.java b/src/test/java/com/arkaya/filedownloader/download/task/FileDownloadTaskTest.java index b38072a..134c558 100644 --- a/src/test/java/com/arkaya/filedownloader/download/task/FileDownloadTaskTest.java +++ b/src/test/java/com/arkaya/filedownloader/download/task/FileDownloadTaskTest.java @@ -16,6 +16,7 @@ public class FileDownloadTaskTest { private FileDownloadTask fileDownloadTask; private String fileLocation; + @Before public void setUp() throws Exception { this.fileLocation = File.separator + "tmp";