/*************************************************** * * cismet GmbH, Saarbruecken, Germany * * ... and it just works. * ****************************************************/ /* * Copyright (C) 2011 jweintraut * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package de.cismet.tools.gui.downloadmanager; import org.apache.log4j.Logger; import org.openide.util.NbBundle; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.text.MessageFormat; import java.util.Observable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadPoolExecutor; import javax.swing.JPanel; import de.cismet.commons.concurrency.CismetConcurrency.CismetThreadFactory; import de.cismet.commons.concurrency.CismetExecutors; /** * The objects of this class represent downloads. This class encompasses several default methods which should be the * same for most download which care about single files. * * @author jweintraut * @version $Revision$, $Date$ */ public abstract class AbstractDownload extends Observable implements Download, Runnable, Comparable { //~ Static fields/initializers --------------------------------------------- private static final ExecutorService downloadThreadPool; static { final SecurityManager s = System.getSecurityManager(); final ThreadGroup parent = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); final int initialParallelDownloads = (DownloadManager.instance().getParallelDownloads() == 0) ? 10 : DownloadManager.instance().getParallelDownloads(); final ThreadGroup threadGroup = new ThreadGroup(parent, "DownloadThreadPool"); downloadThreadPool = CismetExecutors.newCachedLimitedThreadPool( initialParallelDownloads, new CismetThreadFactory(threadGroup, "DownloadThreadPool", null), new DownloadRejectExecutionHandler()); } protected static final Logger log = Logger.getLogger(AbstractDownload.class); //~ Instance fields -------------------------------------------------------- protected String directory; protected File fileToSaveTo; protected State status; protected String title; protected Future downloadFuture; protected boolean started = false; protected Exception caughtException; //~ Methods ---------------------------------------------------------------- /** * Returns the title of the download. * * @return The title. */ @Override public String getTitle() { return title; } /** * Returns a file object pointing to the download location of this download. * * @return A file object pointing to the download location. */ @Override public File getFileToSaveTo() { return fileToSaveTo; } /** * Returns the status of this download. * * @return The status of this download. */ @Override public State getStatus() { return status; } /** * Returns the exception which is caught during the download. If an exception occurs the download is aborted. * * @return The caught exception. */ @Override public Exception getCaughtException() { return caughtException; } @Override public int getDownloadsTotal() { return 1; } @Override public int getDownloadsCompleted() { if (status == State.RUNNING) { return 0; } return 1; } @Override public int getDownloadsErroneous() { if (status == State.COMPLETED_WITH_ERROR) { return 1; } return 0; } /** * Logs a caught exception and sets some members accordingly. * * @param exception The caught exception. */ protected void error(final Exception exception) { if (fileToSaveTo != null) { log.error("Exception occurred while downloading '" + fileToSaveTo + "'.", exception); fileToSaveTo.deleteOnExit(); } else { log.error("Exception occurred while download.", exception); } caughtException = exception; status = State.COMPLETED_WITH_ERROR; stateChanged(); } /** * Starts a thread which starts the download by starting this Runnable. */ @Override public void startDownload() { if (!started) { started = true; if (downloadThreadPool != null) { downloadFuture = downloadThreadPool.submit(this); } else { log.error("Download Thread Pool is null. Downlaod can not be started"); error(new IllegalStateException("downloadThread is null. Can not start Download")); } } } @Override public JPanel getExceptionPanel(final Exception exception) { return null; } @Override public abstract void run(); /** * Determines the destination file for this download. There exist given parameters like a download destination and a * pattern for the file name. It's possible that a previous download with equal parameters still exists physically, * therefore this method adds a counter (2..999) which is appended to the filename. This counter is surrounded by * parentheses. * * @param filename The file name for this download. * @param extension The extension for the downloaded file. */ protected void determineDestinationFile(final String filename, final String extension) { final File directoryToSaveTo; if (isAbsolute(directory)) { // the directory is a absolute path, so it should be used as destination directory directoryToSaveTo = new File(directory); } else { if ((directory != null) && (directory.trim().length() > 0)) { directoryToSaveTo = new File(DownloadManager.instance().getDestinationDirectory(), directory); } else { directoryToSaveTo = DownloadManager.instance().getDestinationDirectory(); } } if (log.isDebugEnabled()) { log.debug("Determined path '" + directoryToSaveTo + "' for file '" + filename + extension + "'."); } if (!directoryToSaveTo.exists()) { if (!directoryToSaveTo.mkdirs()) { log.error("Couldn't create destination directory '" + directoryToSaveTo.getAbsolutePath() + "'. Cancelling download."); error(new Exception( "Couldn't create destination directory '" + directoryToSaveTo.getAbsolutePath() + "'. Cancelling download.")); return; } } if (!directoryToSaveTo.canWrite()) { log.error("Can not write to " + directoryToSaveTo.getAbsolutePath() + ". Probably write permissions are missing."); final String errorMessage = NbBundle.getMessage( AbstractDownload.class, "AbstractDownload.determineDestinationFile().canNotWriteToDirectory"); final Exception ex = new Exception(); ex.getLocalizedMessage(); error(new Exception(MessageFormat.format(errorMessage, directoryToSaveTo.getAbsolutePath()))); return; } fileToSaveTo = new File(directoryToSaveTo, filename + extension); boolean fileFound = false; int counter = 2; while (!fileFound) { while (fileToSaveTo.exists() && (counter < 1000)) { fileToSaveTo = new File(directoryToSaveTo, filename + "(" + counter + ")" + extension); counter++; } try { if (!fileToSaveTo.getParentFile().exists()) { fileToSaveTo.getParentFile().mkdirs(); } fileToSaveTo.createNewFile(); if (fileToSaveTo.exists() && fileToSaveTo.isFile() && fileToSaveTo.canWrite()) { fileFound = true; } } catch (IOException ex) { log.warn("IOEXception while trying to create destination file '" + fileToSaveTo.getAbsolutePath() + "'.", ex); fileToSaveTo.deleteOnExit(); } if ((counter >= 1000) && !fileFound) { log.error("Could not create a file for the download. The tested path is '" + directoryToSaveTo.getAbsolutePath() + File.separatorChar + filename + "<1.." + 999 + ">." + extension + "."); error(new FileNotFoundException( "Could not create a file for the download. The tested path is '" + directoryToSaveTo.getAbsolutePath() + File.separatorChar + filename + "<1.." + 999 + ">." + extension + ".")); return; } } } /** * Checks, if the given directory is absolute. * * @param directory The directory to check * * @return true, iff the directory is absolute */ private boolean isAbsolute(final String directory) { if ((directory != null) && (directory.trim().length() > 1)) { final File path = new File(directory); return path.isAbsolute(); } return false; } // @Override // public boolean cancel() { // final boolean flag = f.cancel(true); // if (!flag) { // log.fatal("could not cancel download thread"); // } // this.status = State.ABORTED; // stateChanged(); // return f.isCancelled(); // } /** * Marks this observable as changed and notifies observers. */ protected void stateChanged() { setChanged(); notifyObservers(); } /** * DOCUMENT ME! */ protected void titleChanged() { setChanged(); notifyObservers(getTitle()); } @Override public boolean equals(final Object obj) { if (!(obj instanceof AbstractDownload)) { return false; } final AbstractDownload other = (AbstractDownload)obj; boolean result = true; if ((this.fileToSaveTo == null) ? (other.fileToSaveTo != null) : (!this.fileToSaveTo.equals(other.fileToSaveTo))) { result &= false; } return result; } @Override public int hashCode() { int hash = 7; hash = (43 * hash) + ((this.fileToSaveTo != null) ? this.fileToSaveTo.hashCode() : 0); return hash; } @Override public int compareTo(final Object o) { if (!(o instanceof AbstractDownload)) { return 1; } final AbstractDownload other = (AbstractDownload)o; return this.title.compareTo(other.title); } /** * DOCUMENT ME! * * @param parallelDownloads DOCUMENT ME! */ public static void setParallelDownloads(final int parallelDownloads) { if (downloadThreadPool == null) { log.error("Thread pool for downloads is null. Currently it is not possible to start downloads"); } if (downloadThreadPool instanceof ThreadPoolExecutor) { ((ThreadPoolExecutor)downloadThreadPool).setMaximumPoolSize(parallelDownloads); } } //~ Inner Classes ---------------------------------------------------------- /** * DOCUMENT ME! * * @version $Revision$, $Date$ */ private static final class DownloadRejectExecutionHandler implements RejectedExecutionHandler { //~ Methods ------------------------------------------------------------ @Override public void rejectedExecution(final Runnable r, final ThreadPoolExecutor executor) { log.error("Execution of Downlaod Thread was rejected."); if (r instanceof AbstractDownload) { final AbstractDownload download = (AbstractDownload)r; download.error(new RejectedExecutionException( " Downlaod konnte nicht gestartet werden. Es stehen nicht genĂ¼gend DownlaodThreads bereit.")); } } } }