/* The MIT License (MIT) * Copyright (c) 2014 Nicholas Wright * http://opensource.org/licenses/MIT */ package com.github.dozedoff.commonj.net; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.LinkedList; import java.util.concurrent.LinkedBlockingQueue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Class for downloading files from the Internet. */ public class FileLoader { private static final Logger logger = LoggerFactory.getLogger(FileLoader.class); protected LinkedBlockingQueue<DownloadItem> downloadList = new LinkedBlockingQueue<DownloadItem>(); private LinkedList<DownloadWorker> workers = new LinkedList<>(); /** Delay between downloads. This is used to limit the number of connections **/ protected int downloadSleep = 1000; protected int fileQueueWorkers; private DataDownloader dataDownloader; private FileLoaderAction actions; protected File workingDir; /** * Use {@link FileLoader#FileLoader(File, int, DataDownloader) instead.} * @param workingDir * @param fileQueueWorkers */ // TODO REMOVE after 0.1.1 @Deprecated public FileLoader(File workingDir, int fileQueueWorkers) { this(workingDir,fileQueueWorkers,new GetBinary()); } // TODO REMOVE after 0.1.1 @Deprecated public FileLoader(File workingDir, int fileQueueWorkers, DataDownloader dataDownloader) { // TODO when this constructor is removed, change the protected methods to private this(workingDir, fileQueueWorkers, dataDownloader, new FileLoaderActionDefault()); } public FileLoader(File workingDir, int fileQueueWorkers, DataDownloader dataDownloader, FileLoaderAction actions) { this.workingDir = workingDir; this.fileQueueWorkers = fileQueueWorkers; this.dataDownloader = dataDownloader; this.actions = actions; setUp(fileQueueWorkers); } /** * Run before a file is added to the list. * * @param url * URL that was added * @param fileName * relative path to working directory * @return if true operation will continue */ protected boolean beforeFileAdd(URL url, String fileName) { return actions.beforeFileAdd(url, fileName); } /** * Run after a file was added to the list. * * @param url * URL that was added * @param fileName * relative path to working directory */ protected void afterFileAdd(URL url, String fileName) { actions.afterFileAdd(url, fileName); } public void add(URL url, String fileName) { if (!beforeFileAdd(url, fileName)) { return; } DownloadItem toadd = new DownloadItem(url, fileName); if (downloadList.contains(toadd)) { logger.info("Ignoring {} ({}), already in download queue", toadd.getImageUrl(), toadd.getImageName()); return; } downloadList.add(toadd); afterFileAdd(url, fileName); } /** * Set the delay between file downloads. Used to limit the number of connections. * * @param sleep * time between downloads in milliseconds */ public void setDownloadSleep(int sleep) { this.downloadSleep = sleep; } public void clearQueue() { downloadList.clear(); logger.info("Download queue cleared"); afterClearQueue(); } /** * Called after the queue has been cleared. */ protected void afterClearQueue() { actions.afterClearQueue(); } /** * Download a file, how the data is used is handled in the method afterFileDownload * * @param url * URL to save * @param savePath * relative save path */ private void loadFile(URL url, File savePath) { File fullPath = new File(workingDir, savePath.toString()); try { Thread.sleep(downloadSleep); } catch (InterruptedException ie) { } byte[] data = null; try { data = dataDownloader.download(url); afterFileDownload(data, fullPath, url); } catch (PageLoadException ple) { onPageLoadException(ple); } catch (IOException ioe) { onIOException(ioe); } } /** * Called when a server could be contacted, but an error code was returned. * * @param ple * the PageLoadException that was thrown */ protected void onPageLoadException(PageLoadException ple) { actions.onPageLoadException(ple); } /** * Called when a page / File could not be loaded due to an IO error. * * @param ioe * the IOException that was thrown */ protected void onIOException(IOException ioe) { actions.onIOException(ioe); } /** * Called when the file was successfully downloaded. * * @param data * the downloaded file * @param fullpath * the absolute filepath * @param url * the url of the file */ protected void afterFileDownload(byte[] data, File fullpath, URL url) { actions.afterFileDownload(data, fullpath, url); } private void setUp(int fileWorkers) { logger.debug("Setting up FileLoader with {} workers", fileWorkers); logger.debug("Creating worker threads"); for (int i = 0; i < fileWorkers; i++) { workers.add(new DownloadWorker()); } logger.debug("Starting worker threads"); for (DownloadWorker t : workers) { t.start(); } logger.debug("FileLoader setup complete"); } public void shutdown() { logger.info("Shutting down FileLoader..."); clearQueue(); logger.debug("Stopping worker threads..."); for (DownloadWorker t : workers) { t.kill(); t.interrupt(); } logger.debug("Waiting for worker threads to die..."); for (DownloadWorker t : workers) { try { t.join(); } catch (InterruptedException e) { t.interrupt(); } } logger.debug("FileLoader shutdown complete"); } /** * Called after a worker has processed an item from the list. * * @param di * the DownloadItem that was processed */ protected void afterProcessItem(DownloadItem di) { actions.afterProcessItem(di); } /** * Called before a worker processes an item from the list. * * @param di * the DownloadItem that will be processed * @return return true if the item should be processed, or false to discard */ protected boolean beforeProcessItem(DownloadItem di) { return actions.beforeProcessItem(di); } class DownloadWorker extends Thread { private boolean stopped = false; public DownloadWorker() { super("Download Worker"); Thread.currentThread().setPriority(2); } public void kill() { this.stopped = true; } @Override public void run() { DownloadItem di = null; while (!stopped) { try { di = downloadList.take(); if (!beforeProcessItem(di)) { continue; } loadFile(di.getImageUrl(), new File(di.getImageName())); afterProcessItem(di); } catch (InterruptedException ie) { interrupt(); // otherwise it will reset it's own interrupt flag logger.debug("FileLoader download worker was interrupted"); } catch (Exception e) { Object[] logParams = { e, di.getImageUrl(), di.getImageName() }; logger.error("Download Worker failed with {}, Parameters: URL: {} ImageName: {}", logParams); } } logger.debug("FileLoader Download worker has died gracefully"); } } }