/** * Downloader.java * * Created on 25.08.2010 */ package org.mage.plugins.card.dl; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ConnectException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import javax.swing.BoundedRangeModel; import org.apache.log4j.Logger; import org.jetlang.channels.Channel; import org.jetlang.channels.MemoryChannel; import org.jetlang.core.Callback; import org.jetlang.core.Disposable; import org.jetlang.fibers.Fiber; import org.jetlang.fibers.PoolFiberFactory; import org.mage.plugins.card.dl.DownloadJob.Destination; import org.mage.plugins.card.dl.DownloadJob.Source; import org.mage.plugins.card.dl.DownloadJob.State; import org.mage.plugins.card.dl.lm.AbstractLaternaBean; /** * The class Downloader. * * @version V0.0 25.08.2010 * @author Clemens Koza */ public class Downloader extends AbstractLaternaBean implements Disposable { private static final Logger logger = Logger.getLogger(Downloader.class); private final List<DownloadJob> jobs = properties.list("jobs"); private final Channel<DownloadJob> channel = new MemoryChannel<>(); private final ExecutorService pool = Executors.newCachedThreadPool(); private final List<Fiber> fibers = new ArrayList<>(); public Downloader() { PoolFiberFactory f = new PoolFiberFactory(pool); //subscribe multiple fibers for parallel execution for (int i = 0, numThreads = 10; i < numThreads; i++) { Fiber fiber = f.create(); fiber.start(); fibers.add(fiber); channel.subscribe(fiber, new DownloadCallback()); } } @Override public void dispose() { for (DownloadJob j : jobs) { switch (j.getState()) { case NEW: case WORKING: j.setState(State.ABORTED); } } for (Fiber f : fibers) { f.dispose(); } pool.shutdown(); } /** * * @throws Throwable */ @Override protected void finalize() throws Throwable { dispose(); super.finalize(); } public void add(DownloadJob job) { if (job.getState() == State.WORKING) { throw new IllegalArgumentException("Job already running"); } if (job.getState() == State.FINISHED) { throw new IllegalArgumentException("Job already finished"); } job.setState(State.NEW); jobs.add(job); channel.publish(job); } public List<DownloadJob> getJobs() { return jobs; } /** * Performs the download job: Transfers data from {@link Source} to * {@link Destination} and updates the download job's state to reflect the * progress. */ private class DownloadCallback implements Callback<DownloadJob> { @Override public void onMessage(DownloadJob job) { //the job won't be processed by multiple threads synchronized (job) { if (job.getState() != State.NEW) { return; } job.setState(State.WORKING); } try { Source src = job.getSource(); Destination dst = job.getDestination(); BoundedRangeModel progress = job.getProgress(); if (dst.isValid()) { progress.setMaximum(1); progress.setValue(1); } else { if (dst.exists()) { try { dst.delete(); } catch (IOException ex1) { logger.warn("While deleting not valid file", ex1); } } progress.setMaximum(src.length()); InputStream is = new BufferedInputStream(src.open()); try { OutputStream os = new BufferedOutputStream(dst.open()); try { byte[] buf = new byte[8 * 1024]; int total = 0; for (int len; (len = is.read(buf)) != -1;) { if (job.getState() == State.ABORTED) { throw new IOException("Job was aborted"); } progress.setValue(total += len); os.write(buf, 0, len); } } catch (IOException ex) { try { dst.delete(); } catch (IOException ex1) { logger.warn("While deleting", ex1); } throw ex; } finally { try { os.close(); if (!dst.isValid()) { dst.delete(); logger.warn("Resource not found " + job.getName() + " from " + job.getSource().toString()); } } catch (IOException ex) { logger.warn("While closing", ex); } } } finally { try { is.close(); } catch (IOException ex) { logger.warn("While closing", ex); } } } job.setState(State.FINISHED); } catch (ConnectException ex) { String message; if (ex.getMessage() != null) { message = ex.getMessage(); } else { message = "Unknown error"; } logger.warn("Error resource download " + job.getName() + " from " + job.getSource().toString() + ": " + message); } catch (IOException ex) { job.setError(ex); } } } }