Package org.rssowl.contrib.podcast.core.download

Source Code of org.rssowl.contrib.podcast.core.download.DownloadService

/*   **********************************************************************  **
**   Copyright notice                                                       **
**                                                                          **
**   (c) 2005-2006 RSSOwl Development Team                                  **
**   http://www.rssowl.org/                                                 **
**                                                                          **
**   All rights reserved                                                    **
**                                                                          **
**   This program and the accompanying materials are made available under   **
**   the terms of the Eclipse Public License v1.0 which accompanies this    **
**   distribution, and is available at:                                     **
**   http://www.rssowl.org/legal/epl-v10.html                               **
**                                                                          **
**   A copy is found in the file epl-v10.html and important notices to the  **
**   license from the team is found in the textfile LICENSE.txt distributed **
**   in this package.                                                       **
**                                                                          **
**   This copyright notice MUST APPEAR in all copies of the file!           **
**                                                                          **
**   Contributors:                                                          **
**     Christophe Bouhier - podcast plugin                         **
**                                                                          **
**  **********************************************************************  */

package org.rssowl.contrib.podcast.core.download;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.swt.widgets.Shell;
import org.rssowl.contrib.podcast.Activator;
import org.rssowl.contrib.podcast.core.net.INetTaskListener;
import org.rssowl.contrib.podcast.core.net.NetTask;
import org.rssowl.contrib.podcast.core.net.NetTaskEvent;
import org.rssowl.contrib.podcast.model.IPersonalAttachment;
import org.rssowl.contrib.podcast.model.IPersonalBookMark;
import org.rssowl.contrib.podcast.model.Model;
import org.rssowl.contrib.podcast.util.Logger;
import org.rssowl.contrib.podcast.util.PulseService;
import org.rssowl.core.persist.IAttachment;
import org.rssowl.core.persist.IBookMark;
import org.rssowl.core.persist.IFeed;
import org.rssowl.core.util.ITask;
import org.rssowl.core.util.JobQueue;

/**
* A download service. It manages a queue of downloads.
*
* This service registers itself as pulse listener. TODO, should de-register
* from the pulse. TODO, Notification of changes in the model should be closer
* to the collection.
*
* @author <a href="mailto:christophe@kualasoft.com">Christophe Bouhier </a>
* @author <a href="mailto:andreas.schaefer@madplanet.com">Andreas Schaefer </a>
* @version 1.1
*/
public class DownloadService implements INetTaskListener, ActionListener {

  public static final int CONNECTING = 0; // Active state

  public static final int DOWNLOADING = 1; // Active state
  public static final int RETRYING = 2; // Active state

  public static final int ERROR = 3; // Passive state

  public static final int CANCELLED = 4; // Passive state

  public static final int COMPLETED = 5; // Passive state

  public static final int QUEUED = 6; // Active state

  public static final int IDLE = 7; // Passive state

  public static final int PAUZED = 8; // Passive state

  public static final int RELEASING = 9; // Activate state

  public static final String[] STATE_DESCRIPTION = { "CONNECTING",
      "DOWNLOADING", "RETRYING", "ERROR", "CANCELLED", "COMPLETED",
      "QUEUED", "IDLE", "PAUZED", "RELEASING" };

  /* Max. number of concurrent running reload Jobs */
  private static final int MAX_CONCURRENT_DOWNLOAD_JOBS = 5;

  /* Queue for downloading from Feeds */
  private final JobQueue fDownloadQueue;

  /* list of downloads */
  private List<IDownload> mDownloadList = new CopyOnWriteArrayList<IDownload>();
  private List<IDownloadListener> mDownloadListeners = new CopyOnWriteArrayList<IDownloadListener>();

  private static DownloadService sSelf;

  private boolean mPauzed = false;

  private Logger mLog = new Logger(DownloadService.class.getName());

  public static DownloadService getInstance() {
    if (sSelf == null) {
      sSelf = new DownloadService();
    }
    return sSelf;
  }
// Signature changed, added maxqueue size.
  public DownloadService() {
    fDownloadQueue = new JobQueue("Downloading podcasts",
        MAX_CONCURRENT_DOWNLOAD_JOBS, 5, true, 0);
    PulseService.getInstance().addEverySecondListener(this);
  }

  public void stopService() {
    PulseService.getInstance().removeEverySecondListener(this);
  }

  protected void notifyDownloadEvent(DownloadEvent pEvent) {
    Iterator<IDownloadListener> lIt = mDownloadListeners.iterator();
    for (; lIt.hasNext();) {
      IDownloadListener lListener = lIt.next();
      if (mDownloadListeners.contains(lListener)) {
        lListener.modelChanged(pEvent);
      }
    }
  }

  public void queueJob(ITask pTask) {

    /* Check if Task is not yet Queued already */
    if (!fDownloadQueue.isQueued(pTask))
      fDownloadQueue.schedule(pTask);
  }

  /**
   * The attachment should be of type IPersonalAttachment. We create one if
   * needed.
   *
   * @param pAttachment
   * @param shell
   */
  public void queueJob(IPersonalAttachment pAttachment, final Shell shell) {

    Download lTask = null;
    IPersonalAttachment lPAttachment;
    if (!(pAttachment instanceof IPersonalAttachment)) {
      lPAttachment = (IPersonalAttachment) pAttachment;
    }else{
      lPAttachment = pAttachment;
    }
    lTask = new Download(lPAttachment, shell, ITask.Priority.DEFAULT);
    /* Check if Task is not yet Queued already */
    if (!fDownloadQueue.isQueued(lTask))
      fDownloadQueue.schedule(lTask);
  }
 
  /**
   * @param pListener
   */
  public void addListener(IDownloadListener pListener) {
    if (!mDownloadListeners.contains(pListener)) {
      mDownloadListeners.add(pListener);
    }
  }

  /**
   * @param pListener
   */
  public void removeListener(IDownloadListener pListener) {
    if (mDownloadListeners.contains(pListener)) {
      mDownloadListeners.remove(pListener);
    }
  }

  /**
   * Add an attachment to be downloaded.
   *
   * @param pPAttachment
   * @return
   */
  /*
   * public IDownload addDownload(IPersonalAttachment pPAttachment) { Download
   * lDownload = new Download(pPAttachment);
   *
   * download(lDownload); return lDownload; }
   */
  public void abort(IPersonalBookMark pPBookMark) {
    for (IDownload lDownload : mDownloadList) {

      IFeed lFeed = lDownload.getAttachment().getNews()
          .getFeedReference().resolve();
      IBookMark lPBookMark = Model.getInstance().getBookMark(lFeed);
      if (lPBookMark.equals(pPBookMark)) { // CB THIS equatation will
        // not work.
        abort(lDownload);
      }
    }
  }

  public void abort(IDownload pDownload) {
    if (pDownload != null) {
      int lState = pDownload.getState();
      if (lState != COMPLETED || lState != ERROR) {
        pDownload.setState(CANCELLED);
        NetTask.getInstance().notifyNetworkEvent(
            new NetTaskEvent(pDownload,
                NetTaskEvent.DOWNLOAD_STATUS_CHANGED));

      }
    }
  }

  /**
   * This method will create the folder and file to store the download. it
   * will also handle possible errors in preparing the download file.
   * Subsequently the download object is handed over to the network function.
   *
   * @param pDownload
   *            Downloads.Download
   */
  public IStatus download(IDownload pDownload) {

    if (mPauzed) {
      return Status.CANCEL_STATUS; // Downloads are not added in status
      // pauzed.
    }
    if(!mDownloadList.contains(pDownload)){
      mDownloadList.add(mDownloadList.size(), pDownload)
      notifyDownloadEvent(new DownloadEvent(this));
      IPersonalAttachment lAttach = pDownload.getAttachment();
    }

    try {
      int lState = pDownload.getState();
      if (lState == CANCELLED) {
        return Status.CANCEL_STATUS;
      } else {
        pDownload.setState(QUEUED);
        NetTask.getInstance().downLoad(pDownload);
      }
    } catch (Exception ie) {
      mLog.warn(ie.getMessage());
    }
    return Status.OK_STATUS;
  }

  /**
   * Change the status of all downloads to pauze.
   */
  public void pauzeAll() {
    mPauzed = true;
    for (IDownload lDownload : mDownloadList) {
      int lState = lDownload.getState();
      if (lState == DOWNLOADING || lState == QUEUED
          || lState == CONNECTING || lState == RETRYING) {
        lDownload.setState(PAUZED);
        NetTask.getInstance().notifyNetworkEvent(
            new NetTaskEvent(lDownload,
                NetTaskEvent.DOWNLOAD_STATUS_CHANGED));
      }
    }
   
  }

  /**
   * Change the status of all downloads to pauze.
   */
  public void resumeAll() {
    mPauzed = false;
    for (IDownload lDownload : mDownloadList) {
      if (lDownload.getState() == PAUZED) {
        lDownload.resetSpeed();
        Download lTask = (Download)lDownload;
        queueJob(lTask);
      }
    }
  }

  /**
   * Clean all completed, errorneous and cancelled downloads.
   */
  public void cleanAllCompleted() {
    int lState;
    for (IDownload download : mDownloadList) {
      lState = download.getState();
      if (lState == COMPLETED || lState == ERROR || lState == CANCELLED) {
        mDownloadList.remove(download);
      }
    }
    notifyDownloadEvent(new DownloadEvent(this));
  }

  /**
   * Get a download class, based on the index.
   *
   * @param int
   *            pIndex
   * @return Download
   */
  public IDownload getDownload(int pIndex) {
    return (IDownload) mDownloadList.get(pIndex);
  }

  public boolean isValidIndex(int pIndex) {
    try {
      mDownloadList.get(pIndex);
      return true;
    } catch (IndexOutOfBoundsException e) {
      return false;
    }
  }

  /**
   * Get a download class, based on the enclosure.
   *
   * @param pAttachment
   *            Enclosure
   * @return Download
   */
  public IDownload getDownload(IAttachment pAttachment) {
    for (IDownload lDownload : mDownloadList) {
      if (lDownload.getAttachment() == pAttachment) {
        return lDownload;
      }
    }
    return null;
  }

  /**
   * Get the download list.
   *
   * @return Object[]
   */
  public Object[] getDownloadArray() {
    return mDownloadList.toArray();
  }

  public int getDownloadIndexOf(IDownload pDownload) {
    return mDownloadList.indexOf(pDownload);
  }

  /**
   * Get the download list.
   *
   * @return LinkedList
   */
  public Iterator<IDownload> getDownloadIterator() {
    return mDownloadList.iterator();
  }

  public List<IDownload> getDownloadList() {
    return mDownloadList;
  }

  /**
   * The number of currently ongoing downloads;
   *
   * @return int
   */
  public int getNumberOfPauzedDownloads() {
    int count = 0;
    for (IDownload lDownload : mDownloadList) {
      count += lDownload.getState() == PAUZED ? 1 : 0;
    }
    return count;
  }

  /**
   * The number of all entries in the download list.
   *
   * @return int
   */
  public int getNumberOfDownloadItems() {
    return mDownloadList.size();
  }

  /**
   * The number of currently ongoing downloads; including status
   * <code>DOWNLOADING</code>, <code>CONNECTING</code>,
   * <code>QUEUED</code> and <code>RETRYING</code>
   *
   * @return int
   */
  public int getNumberOfActiveDownloads() {
    int count = 0;
    for (IDownload lDownload : mDownloadList) {
      int lState = lDownload.getState();
      if (lState == DOWNLOADING || lState == RETRYING
          || lState == CONNECTING || lState == QUEUED) {
        count++;
      }
    }
    return count;
  }

  public boolean isDownloading() {
    return getNumberOfActiveDownloads() == 0;
  }

  /**
   * Get the index of the download from the service.
   *
   * @param pDownload
   * @return
   */
  public int indexOf(IDownload pDownload) {
    return mDownloadList.indexOf(pDownload);
  }

  /**
   * Query if a specific enclosure is in the download list and the status is
   * downloading. This convenience class of of particular use before
   * initiating a download to avoid a download conflict.
   *
   * @param pAttachment
   * @return boolean If the enclosure is being downloaded.
   */
  public boolean isDownloading(IPersonalAttachment pPAttachment) {
    for (IDownload lDownload : mDownloadList) {
      // CB TODO, check if the equal code complies to
      // comparing the URL?

      if (lDownload.getAttachment().equals(pPAttachment)) {
        return true;
      } else {
        continue;
      }
      // CB FIXME remove later, should be embedded in the attachement
      // model.
      // try {
      // file = dEncl.getURL().getFile();
      // String cfile = encl.getURL().getFile();
      // if (file.equals(cfile)) {
      // return true;
      // }
      // } catch (XEnclosureException e) {
      // }
    }
    return false;
  }

  /**
   * Retry a download.(Not possible in status pauzed).
   */
  public void retry(IDownload pDownload) {
    if (!mPauzed) {
      resetDownload(pDownload);
      // CB TODO, We can't call download here. Use queue  job instead. 
      // download(pDownload);
     
    } else {
      Activator.getDefault().getLog().log(
          new Status(IStatus.INFO, getClass().getName(), IStatus.OK,
              "Retry not allowed in status pauzed", null));
    }
  }

  /**
   * Check the status of this service.
   *
   * @return
   */
  public boolean getPauzed() {
    return mPauzed;
  }

  /**
   * Reset the download object. (Basically same as a new download object).
   *
   * @param lDownload
   * @return
   */
  public IDownload resetDownload(IDownload pDownload) {

    boolean match = false;
    for (IDownload lDownload : mDownloadList) {
      if (lDownload.equals(lDownload)) {
        match = true;
        break;
      }
    }
    if (match) {
      pDownload.setState(IDLE);
      pDownload.resetTimeElapsed();
      pDownload.resetSpeed();
      pDownload.setCurrent(0);
    }
    return pDownload;
  }

  /**
   * Listen for network events.
   *
   * @see com.jpodder.net.INetTaskListener#netActionPerformed(com.jpodder.net.NetTaskEvent)
   */
  public void netActionPerformed(NetTaskEvent event) {

    Download lDownload = (Download) event.getSource();
    int lState = lDownload.getState();
    IPersonalAttachment lAttachment = lDownload.getAttachment();

    if (event.getNetEvent() == NetTaskEvent.DOWNLOAD_SUCCESS) {

      // CB TODO, decide what to do with the cache.
      // Cache.getInstance().addTrack(lEnclosure.getFile());
      lAttachment.setCached(true);
      lAttachment.setDownloadCompleted(true);

      lAttachment.setLocal(true);

      lAttachment.setMarked(false);
      lAttachment.setCandidate(false);

      // CB TODO, review if this is the option.
      // lEnclosure.getFeed().setCandidatesCount(
      // lEnclosure.getFeed().getCandidatesCount() - 1);

      mLog.info("Download of: " + lAttachment.getFile() + " completed");

      // CB TODO Migrate post-download handling.
      // if (ContentLogic.isTorrent(lEnclosure.getFile().getName())) {
      // mLog.info("Process torrent file.");
      // lEnclosure.setTorrent();
      // } else {
      // mLog.info("Process file (non-torrent).");
      //
      // // CB TODO In Concurrency, we would like to exit this thread and
      // // let another thread post-process the download. Using
      // // semaphores? Also which tasks are executed should be
      // // configurable.
      //
      // ID3Logic.getInstance().rewriteTags(lEnclosure);
      // PlayerLogic.getInstance().storeInPlayer(lEnclosure);
      // }
    }

    if (event.getNetEvent() == NetTaskEvent.DOWNLOAD_STATUS_CHANGED) {
      mLog.info("Download of: " + lAttachment + " changed state to: "
          + STATE_DESCRIPTION[lState]);
      if (event.getNetEvent() == NetTaskEvent.DOWNLOAD_FAILED) {
        mLog.error("Error while downloading " + lAttachment);
        // retry the download.
        if (event.getException() != null) {
          String message = event.getException().getMessage();
          mLog.error(message);
          if (message != null) {
            if (message.startsWith("Read timed")
                || message.startsWith("Partial content")
                || message.startsWith("Connection reset")
                || message.startsWith("Connection timed out")) {
              DownloadsRetry lRetry = new DownloadsRetry();
              lRetry.retryLater(lDownload);
            }
          }
        }
        // Delete the file if the download failed totally (size =0).
        File file = lAttachment.getFile();
        if (file != null && file.exists() && file.length() == 0) {
          file.delete();
        }
      }

      // Do some state updating on the model.
      switch (lState) {
      case DOWNLOADING:
      case QUEUED:
      case CONNECTING:
      case RELEASING:
        break;
      case RETRYING:
      case PAUZED: {
        lDownload.mSpeed = 0;
      }
        break;
      case ERROR:
      case CANCELLED: {
        lDownload.mSpeed = 0;
      }
        break;
      case COMPLETED: {
        lDownload.mSpeed = 0;
        // CB TODO, migrate
        // if (Configuration.getInstance().getSound()) {
        // Toolkit.getDefaultToolkit().beep();
        // }
      }
        break;

      }
    }
  }

  /**
   * Update elapsed time (+ 1 second) and bandwith for each download.
   *
   */
  public void heartBeat() {
    synchronized (mDownloadList) {

      for (IDownload lDownload : mDownloadList) {
        switch (lDownload.getState()) {
        case COMPLETED:
        case QUEUED:
        case CANCELLED:
        case PAUZED:
        case ERROR:
          break;
        case CONNECTING:
        case RELEASING:
        case RETRYING: {
          lDownload.incrementSecond();
          break;
        }
        case DOWNLOADING: {
          lDownload.incrementSecond();
          lDownload.calculateSpeed();
          if (lDownload.getPrevious() != lDownload.getCurrent()) {
            lDownload.setPrevious(lDownload.getCurrent());
          }
        }
          break;
        }
      }
    }
  }

  /**
   * Hearbeat action.
   */
  public void actionPerformed(ActionEvent e) {
    heartBeat();
  }
}
TOP

Related Classes of org.rssowl.contrib.podcast.core.download.DownloadService

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.