Package org.rssowl.ui.internal.services

Source Code of org.rssowl.ui.internal.services.DownloadService

/*   **********************************************************************  **
**   Copyright notice                                                       **
**                                                                          **
**   (c) 2005-2009 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:                                                          **
**     RSSOwl Development Team - initial API and implementation             **
**                                                                          **
**  **********************************************************************  */

package org.rssowl.ui.internal.services;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.window.Window;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.program.Program;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.progress.IProgressConstants;
import org.rssowl.core.Owl;
import org.rssowl.core.connection.AuthenticationRequiredException;
import org.rssowl.core.connection.ConnectionException;
import org.rssowl.core.connection.CredentialsException;
import org.rssowl.core.connection.HttpConnectionInputStream;
import org.rssowl.core.connection.IAbortable;
import org.rssowl.core.connection.IConnectionPropertyConstants;
import org.rssowl.core.connection.IProtocolHandler;
import org.rssowl.core.internal.persist.pref.DefaultPreferences;
import org.rssowl.core.persist.IAttachment;
import org.rssowl.core.persist.pref.IPreferenceScope;
import org.rssowl.core.util.CoreUtils;
import org.rssowl.core.util.StringUtils;
import org.rssowl.core.util.URIUtils;
import org.rssowl.ui.internal.Activator;
import org.rssowl.ui.internal.Controller;
import org.rssowl.ui.internal.OwlUI;
import org.rssowl.ui.internal.dialogs.LoginDialog;
import org.rssowl.ui.internal.util.DownloadJobQueue;
import org.rssowl.ui.internal.util.JobRunner;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
* A service to download files in a {@link DownloadJobQueue} with proper
* progress reporting.
*
* @author bpasero
*/
public class DownloadService {

  /* Max. number of concurrent Jobs for downloading files */
  private static final int MAX_CONCURRENT_DOWNLOAD_JOBS = 3;

  /* Connection Timeouts in MS */
  private static final int DEFAULT_CON_TIMEOUT = 30000;

  /* Default Length for Download Tasks */
  private static final int DEFAULT_TASK_LENGTH = 1000000;

  /* Default Progress for Download Tasks */
  private static final int DEFAULT_WORKED = 200;

  private DownloadJobQueue fDownloadQueue;
  private Map<OutputStream, OutputStream> fOutputStreamMap = new ConcurrentHashMap<OutputStream, OutputStream>();
  private IPreferenceScope fPreferences = Owl.getPreferenceService().getGlobalScope();

  /* Task for a Download */
  private class AttachmentDownloadTask extends DownloadJobQueue.DownloadTask {
    private final IAttachment fAttachment;
    private final URI fFile;
    private final File fFolder;
    private final boolean fUserInitiated;
    private final String fName;

    private AttachmentDownloadTask(IAttachment attachment, URI file, String name, File folder, boolean userInitiated) {
      fAttachment = attachment;
      fFile = file;
      fName = name;
      fFolder = folder;
      fUserInitiated = userInitiated;
    }

    @Override
    public IStatus run(Job job, IProgressMonitor monitor) {
      return internalDownload(job, fAttachment, fFile, fName, fFolder, monitor, fUserInitiated);
    }

    public String getName() {
      return NLS.bind(Messages.DownloadService_DOWNLOADING_N, fFile.toString());
    }

    public Priority getPriority() {
      return Priority.DEFAULT;
    }

    @Override
    public int hashCode() {
      return fFile.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
      if (this == obj)
        return true;

      if (obj == null)
        return false;

      if (getClass() != obj.getClass())
        return false;

      final AttachmentDownloadTask other = (AttachmentDownloadTask) obj;
      return fFile.equals(other.fFile);
    }
  }

  /** Default Constructor to create a Download Queue */
  public DownloadService() {
    fDownloadQueue = new DownloadJobQueue(Messages.DownloadService_DOWNLOADING_TITLE, MAX_CONCURRENT_DOWNLOAD_JOBS, Integer.MAX_VALUE);
  }

  /**
   * @param attachment the {@link IAttachment} to download.
   * @param file the file to download as {@link URI}.
   * @param folder the folder to download to as {@link File}.
   * @param userInitiated if <code>true</code> the user explicitly asked for a
   * download and <code>false</code> otherwise (e.g. if download is from a
   * filter).
   */
  public void download(IAttachment attachment, URI file, File folder, boolean userInitiated) {
    download(attachment, file, null, folder, userInitiated);
  }

  /**
   * @param attachment the {@link IAttachment} to download.
   * @param file the file to download as {@link URI}.
   * @param name the name of the file to use or <code>null</code> to take this
   * information from the link.
   * @param folder the folder to download to as {@link File}.
   * @param userInitiated if <code>true</code> the user explicitly asked for a
   * download and <code>false</code> otherwise (e.g. if download is from a
   * filter).
   */
  public void download(IAttachment attachment, URI file, String name, File folder, boolean userInitiated) {
    AttachmentDownloadTask task = new AttachmentDownloadTask(attachment, file, name, folder, userInitiated);
    if (!fDownloadQueue.isQueued(task))
      fDownloadQueue.schedule(task);
  }

  private IStatus internalDownload(Job job, final IAttachment attachment, final URI link, final String fileName, final File folder, final IProgressMonitor monitor, final boolean userInitiated) {
    String downloadFileName;
    if (StringUtils.isSet(fileName))
      downloadFileName = fileName;
    else
      downloadFileName = URIUtils.getFile(link, OwlUI.getExtensionForMime(attachment.getType()));

    File downloadFile = new File(folder, downloadFileName);
    job.setName(NLS.bind(Messages.DownloadService_DOWNLOADING, downloadFileName));
    job.setProperty(IProgressConstants.ICON_PROPERTY, OwlUI.getAttachmentImage(downloadFileName, attachment.getType()));

    int bytesConsumed = 0;
    try {
      IProtocolHandler handler = Owl.getConnectionService().getHandler(link);
      if (handler != null) {
        Map<Object, Object> properties = new HashMap<Object, Object>();
        properties.put(IConnectionPropertyConstants.CON_TIMEOUT, DEFAULT_CON_TIMEOUT);

        /* Check for Cancellation and Shutdown */
        if (monitor.isCanceled() || Controller.getDefault().isShuttingDown())
          return Status.CANCEL_STATUS;

        /* Initialize Fields */
        long bytesPerSecond = 0;
        long lastTaskNameUpdate = 0;
        long lastBytesCheck = 0;
        int length = attachment.getLength();
        byte[] buffer = new byte[8192];

        /* Begin Task */
        monitor.beginTask(formatTask(bytesConsumed, length, -1), length > 0 ? length : DEFAULT_TASK_LENGTH);

        /* First Download to a temporary File */
        int contentLength = length;
        InputStream in = null;
        FileOutputStream out = null;
        File partFile = new File(folder, downloadFileName + ".part"); //$NON-NLS-1$
        boolean canceled = false;
        Exception error = null;
        try {

          /* Open Stream */
          in = handler.openStream(link, monitor, properties);

          /* Obtain real Content Length from Stream if available */
          if (in instanceof HttpConnectionInputStream) {
            int len = ((HttpConnectionInputStream) in).getContentLength();
            if (len > 0)
              contentLength = len;
          }

          /* Create tmp part File */
          partFile.createNewFile();
          partFile.deleteOnExit();

          /* Keep Outputstream for later */
          out = new FileOutputStream(partFile);
          fOutputStreamMap.put(out, out);

          /* Download */
          while (true) {

            /* Check for Cancellation and Shutdown */
            if (monitor.isCanceled() || Controller.getDefault().isShuttingDown()) {
              canceled = true;
              return Status.CANCEL_STATUS;
            }

            /* Read from Stream */
            int read = in.read(buffer);
            bytesConsumed += read;
            if (read == -1)
              break;

            /* Write to File */
            out.write(buffer, 0, read);

            /* Update Task Name once per Second */
            long now = System.currentTimeMillis();
            long timeDiff = (now - lastTaskNameUpdate);
            if (timeDiff > 1000) {
              long bytesDiff = bytesConsumed - lastBytesCheck;
              bytesPerSecond = bytesDiff / (timeDiff / 1000);
              monitor.setTaskName(formatTask(bytesConsumed, contentLength, (int) bytesPerSecond));
              lastTaskNameUpdate = now;
              lastBytesCheck = bytesConsumed;
            }

            /* Report accurate progress */
            if (attachment.getLength() > 0)
              monitor.worked(read);

            /* Report calculated progress if possible */
            else if (contentLength > 0) {
              float relWorked = read / (float) contentLength;
              monitor.worked((int) (relWorked * DEFAULT_TASK_LENGTH));
            }

            /* Use a generic Progress Value */
            else
              monitor.worked(DEFAULT_WORKED);
          }
        } catch (FileNotFoundException e) {
          error = e;
          return Activator.getDefault().createErrorStatus(e.getMessage(), e);
        } catch (IOException e) {
          error = e;
          return Activator.getDefault().createErrorStatus(e.getMessage(), e);
        } catch (ConnectionException e) {
          final boolean showError[] = new boolean[] { true };

          /* Offer a Login Dialog if Authentication is Required */
          if (userInitiated && e instanceof AuthenticationRequiredException && !monitor.isCanceled() && !Controller.getDefault().isShuttingDown()) {
            final Shell shell = OwlUI.getActiveShell();
            if (shell != null && !shell.isDisposed()) {
              Controller.getDefault().getLoginDialogLock().lock();
              try {
                final AuthenticationRequiredException authEx = (AuthenticationRequiredException) e;
                JobRunner.runSyncedInUIThread(shell, new Runnable() {
                  public void run() {

                    /* Return on Cancelation or shutdown or deletion */
                    if (monitor.isCanceled() || Controller.getDefault().isShuttingDown())
                      return;

                    /* Credentials might have been provided meanwhile in another dialog */
                    try {
                      URI normalizedUri = URIUtils.normalizeUri(link, true);
                      if (Owl.getConnectionService().getAuthCredentials(normalizedUri, authEx.getRealm()) != null) {
                        fDownloadQueue.schedule(new AttachmentDownloadTask(attachment, link, fileName, folder, userInitiated));
                        showError[0] = false;
                        return;
                      }
                    } catch (CredentialsException exe) {
                      Activator.getDefault().getLog().log(exe.getStatus());
                    }

                    /* Show Login Dialog */
                    LoginDialog login = new LoginDialog(shell, link, authEx.getRealm());
                    if (login.open() == Window.OK && !monitor.isCanceled() && !Controller.getDefault().isShuttingDown()) {
                      fDownloadQueue.schedule(new AttachmentDownloadTask(attachment, link, fileName, folder, userInitiated));
                      showError[0] = false;
                    }
                  }
                });
              } finally {
                Controller.getDefault().getLoginDialogLock().unlock();
              }
            }
          }

          /* User has not Provided Login Credentials or any other error */
          if (showError[0]) {
            error = e;
            return Activator.getDefault().createErrorStatus(e.getMessage(), e);
          }

          /* User has Provided Login Credentials - cancel this Task */
          monitor.setCanceled(true);
          canceled = true;
          return Status.CANCEL_STATUS;
        } finally {
          monitor.done();

          /* Indicate Error Message if any and offer Action to download again */
          if (error != null) {
            String errorMessage = CoreUtils.toMessage(error);
            if (StringUtils.isSet(errorMessage))
              job.setName(NLS.bind(Messages.DownloadService_ERROR_DOWNLOADING_N, downloadFileName, errorMessage));
            else
              job.setName(NLS.bind(Messages.DownloadService_ERROR_DOWNLOADING, downloadFileName));

            job.setProperty(IProgressConstants.ICON_PROPERTY, OwlUI.ERROR);
            job.setProperty(IProgressConstants.ACTION_PROPERTY, getRedownloadAction(new AttachmentDownloadTask(attachment, link, fileName, folder, true)));
            monitor.setTaskName(Messages.DownloadService_TRY_AGAIN);
          }

          /* Close Output Stream */
          if (out != null) {
            try {
              out.close();
              fOutputStreamMap.remove(out);
              if (canceled || error != null)
                partFile.delete();
            } catch (IOException e) {
              return Activator.getDefault().createErrorStatus(e.getMessage(), e);
            }
          }

          /* Close Input Stream */
          if (in != null) {
            try {
              if ((canceled || error != null) && in instanceof IAbortable)
                ((IAbortable) in).abort();
              else
                in.close();
            } catch (IOException e) {
              return Activator.getDefault().createErrorStatus(e.getMessage(), e);
            }
          }
        }

        /* Check for Cancellation and Shutdown */
        if (monitor.isCanceled() || Controller.getDefault().isShuttingDown()) {
          partFile.delete();
          return Status.CANCEL_STATUS;
        }

        /* Now copy over the part file to the actual file in an atomic operation */
        if (!partFile.renameTo(downloadFile)) {
          downloadFile.delete();
          partFile.renameTo(downloadFile);
        }
      }
    } catch (ConnectionException e) {
      return Activator.getDefault().createErrorStatus(e.getMessage(), e);
    }

    /* Offer Action to Open Attachment by keeping Job in Viewer if set */
    if (!fPreferences.getBoolean(DefaultPreferences.HIDE_COMPLETED_DOWNLOADS)) {
      job.setProperty(IProgressConstants.KEEP_PROPERTY, Boolean.TRUE);
      job.setProperty(IProgressConstants.ACTION_PROPERTY, getOpenAction(downloadFile));
    }

    /* Update Job Name */
    if (bytesConsumed > 0)
      job.setName(NLS.bind(Messages.DownloadService_N_OF_M, downloadFileName, OwlUI.getSize(bytesConsumed)));
    else
      job.setName(downloadFileName);

    /* The Label of the Status is used as Link for Action */
    return new Status(IStatus.OK, Activator.PLUGIN_ID, Messages.DownloadService_OPEN_FOLDER);
  }

  private String formatTask(int bytesConsumed, int totalBytes, int bytesPerSecond) {
    StringBuilder str = new StringBuilder();

    /* "Time Remaining" */
    int bytesToGo = totalBytes - bytesConsumed;
    if (bytesToGo > 0 && bytesPerSecond > 0) {
      int secondsRemaining = bytesToGo / bytesPerSecond;
      String period = OwlUI.getPeriod(secondsRemaining);
      if (period != null)
        str.append(NLS.bind(Messages.DownloadService_BYTES_REMAINING, period)).append(" - "); //$NON-NLS-1$
    }

    /* "X MB of Y MB "*/
    String consumed = OwlUI.getSize(bytesConsumed);
    if (consumed == null)
      consumed = "0"; //$NON-NLS-1$

    String total = OwlUI.getSize(totalBytes);
    if (total != null)
      str.append(NLS.bind(Messages.DownloadService_BYTES_OF_BYTES, consumed, total));
    else
      str.append(NLS.bind(Messages.DownloadService_BYTES_OF_UNKNOWN, consumed));

    /* "(X MB/sec)" */
    if (bytesPerSecond > 0) {
      str.append(" "); //$NON-NLS-1$
      str.append(NLS.bind(Messages.DownloadService_BYTES_PER_SECOND, OwlUI.getSize(bytesPerSecond)));
    }

    return str.toString();
  }

  private IAction getOpenAction(final File downloadFile) {
    return new Action(Messages.DownloadService_OPEN_FOLDER) {
      @Override
      public void run() {
        Program.launch(downloadFile.getParent());
      }
    };
  }

  private IAction getRedownloadAction(final AttachmentDownloadTask task) {
    return new Action(Messages.DownloadService_RE_DOWNLOAD) {
      @Override
      public void run() {
        fDownloadQueue.schedule(task);
      }
    };
  }

  /**
   * Stops this Service and cancels all pending downloads.
   */
  public void stopService() {
    fDownloadQueue.cancel(false);

    /* Need to properly close yet opened Streams */
    Set<OutputStream> openStreams = fOutputStreamMap.keySet();
    for (OutputStream out : openStreams) {
      try {
        out.close();
      } catch (IOException e) {
        /* Ignore */}
    }
  }

  /**
   * @return <code>true</code> if there are active download jobs running and
   * <code>false</code> otherwise.
   */
  public boolean isActive() {
    return fDownloadQueue.isWorking();
  }
}
TOP

Related Classes of org.rssowl.ui.internal.services.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.