package org.limewire.core.impl.mozilla; import java.io.File; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import org.limewire.core.api.mozilla.LimeMozillaDownloadManagerListener; import org.limewire.core.api.mozilla.LimeMozillaDownloadProgressListener; import org.limewire.listener.EventListener; import org.limewire.listener.EventListenerList; import org.limewire.logging.Log; import org.limewire.logging.LogFactory; import org.limewire.util.Objects; import org.mozilla.browser.MozillaExecutor; import org.mozilla.interfaces.nsIDOMDocument; import org.mozilla.interfaces.nsIDownload; import org.mozilla.interfaces.nsIDownloadManager; import org.mozilla.interfaces.nsIDownloadProgressListener; import org.mozilla.interfaces.nsIRequest; import org.mozilla.interfaces.nsISupports; import org.mozilla.interfaces.nsIWebProgress; import org.mozilla.xpcom.Mozilla; import org.mozilla.xpcom.XPCOMException; import com.limegroup.gnutella.InsufficientDataException; import com.limegroup.gnutella.Downloader.DownloadState; import com.limegroup.gnutella.downloader.CoreDownloader; import com.limegroup.gnutella.downloader.DownloadStateEvent; import com.limegroup.mozilla.MozillaDownload; /** * This class listens to a specific Mozilla download and tracks some statistics * for us. */ public class LimeMozillaDownloadProgressListenerImpl implements nsIDownloadProgressListener, MozillaDownload, LimeMozillaDownloadProgressListener { private static final Log LOG = LogFactory.getLog(LimeMozillaDownloadProgressListenerImpl.class); private final long downloadId; private final AtomicInteger state; private final AtomicLong totalProgress; private final SimpleBandwidthTracker down; private final File incompleteFile; private final AtomicLong contentLength; private final LimeMozillaDownloadManagerListener manager; private final EventListenerList<DownloadStateEvent> listeners; private final BlockingQueue<DownloadStateEvent> statusEvents; private final ScheduledExecutorService backgroundExecutor; private final XPComUtility xpComUtility; private CoreDownloader downloader; public LimeMozillaDownloadProgressListenerImpl(LimeMozillaDownloadManagerListener manager, ScheduledExecutorService backgroundExecutor, nsIDownload download, XPComUtility xpComUtility) { this.manager = Objects.nonNull(manager, "manager"); this.backgroundExecutor = Objects.nonNull(backgroundExecutor, "backgroundExecutor"); Objects.nonNull(download, "download"); this.listeners = new EventListenerList<DownloadStateEvent>(); this.downloadId = download.getId(); this.state = new AtomicInteger(); this.statusEvents = new LinkedBlockingQueue<DownloadStateEvent>(); this.totalProgress = new AtomicLong(); this.down = new SimpleBandwidthTracker(); this.incompleteFile = new File(download.getTargetFile().getPath()); this.contentLength = new AtomicLong(download.getSize()); this.xpComUtility = xpComUtility; } @Override public synchronized void onDownloadStateChange(short state, nsIDownload download) { if (downloadId == download.getId()) { changeState(state); } } public void init(CoreDownloader coreDownloader, short state) { this.downloader = coreDownloader; changeState(state); } private synchronized void changeState(short state) { if (state != this.state.get()) { this.state.set(state); DownloadState downloadStatus = getDownloadStatus(); DownloadStateEvent downloadStatusEvent = new DownloadStateEvent(downloader, downloadStatus); statusEvents.add(downloadStatusEvent); backgroundExecutor.execute(new Runnable() { @Override public void run() { synchronized (listeners) { DownloadStateEvent event = statusEvents.poll(); if (event != null) { listeners.broadcast(event); } } } }); } } @Override public synchronized void onProgressChange(nsIWebProgress webProgress, nsIRequest request, long curSelfProgress, long maxSelfProgress, long curTotalProgress, long maxTotalProgress, nsIDownload download) { if (this.downloadId == download.getId()) { // this is my download int diff = (int) (curTotalProgress - totalProgress.longValue()); down.count(diff); if (!isPaused()) { // this event might come in after pausing, so we don't want to // change the state in that event short state = download.getState(); changeState(state); } totalProgress.set(curTotalProgress); contentLength.set(download.getSize()); } } @Override public synchronized void onSecurityChange(nsIWebProgress webProgress, nsIRequest request, long state, nsIDownload download) { // don't care about this event. } @Override public synchronized void onStateChange(nsIWebProgress webProgress, nsIRequest request, long stateFlags, long status, nsIDownload download) { if (this.downloadId == download.getId()) { // this is my download changeState(download.getState()); if (this.state.get() == nsIDownloadManager.DOWNLOAD_FINISHED) { this.contentLength.set(download.getSize()); this.totalProgress.set(this.contentLength.get()); } } } @Override public void setDocument(nsIDOMDocument document) { // no mozilla window to use } @Override public nsIDOMDocument getDocument() { // no mozilla window to use return null; } @Override public nsISupports queryInterface(String uuid) { return Mozilla.queryInterface(this, uuid); } public long getDownloadId() { return downloadId; } @Override public synchronized float getAverageBandwidth() { return down.getAverageBandwidth(); } @Override public synchronized float getMeasuredBandwidth() { try { return down.getMeasuredBandwidth(); } catch (InsufficientDataException e) { return 0; } } @Override public synchronized void measureBandwidth() { down.measureBandwidth(); } @Override public synchronized boolean isCompleted() { return state.get() == nsIDownloadManager.DOWNLOAD_FINISHED; } @Override public synchronized boolean isPaused() { int myState = state.get(); boolean paused = myState == nsIDownloadManager.DOWNLOAD_PAUSED; return paused; } @Override public synchronized DownloadState getDownloadStatus() { switch (state.get()) { case nsIDownloadManager.DOWNLOAD_SCANNING: return DownloadState.RESUMING; case nsIDownloadManager.DOWNLOAD_DOWNLOADING: return DownloadState.DOWNLOADING; case nsIDownloadManager.DOWNLOAD_FINISHED: return DownloadState.COMPLETE; case nsIDownloadManager.DOWNLOAD_QUEUED: return DownloadState.QUEUED; case nsIDownloadManager.DOWNLOAD_PAUSED: return DownloadState.PAUSED; case nsIDownloadManager.DOWNLOAD_NOTSTARTED: return DownloadState.PAUSED; case nsIDownloadManager.DOWNLOAD_CANCELED: return DownloadState.ABORTED; case nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL: return DownloadState.INVALID; case nsIDownloadManager.DOWNLOAD_BLOCKED_POLICY: return DownloadState.INVALID; case nsIDownloadManager.DOWNLOAD_DIRTY: return DownloadState.INVALID; case nsIDownloadManager.DOWNLOAD_FAILED: return DownloadState.INVALID; } throw new IllegalStateException("unknown mozilla state"); } @Override public File getIncompleteFile() { return incompleteFile; } @Override public synchronized boolean isInactive() { boolean inactive = state.get() != nsIDownloadManager.DOWNLOAD_DOWNLOADING && state.get() != nsIDownloadManager.DOWNLOAD_SCANNING; return inactive; } @Override public synchronized long getAmountDownloaded() { return totalProgress.get(); } @Override public synchronized long getAmountPending() { return getContentLength() - getAmountDownloaded(); } @Override public synchronized long getContentLength() { return contentLength.get(); } @Override public void cancelDownload() { MozillaExecutor.mozSyncExec(new Runnable() { @Override public void run() { if (!LimeMozillaDownloadProgressListenerImpl.this.isCancelled()) { try { synchronized (LimeMozillaDownloadProgressListenerImpl.this) { getDownloadManager().cancelDownload(downloadId); } } catch (XPCOMException e) { //TODO catching this exception because user in ui can click multiple times and mozilla code cannot handle that //should revisit and figure out a better way of ahndling this issue LOG.debug(e.getMessage(), e); } } } }); } @Override public void pauseDownload() { MozillaExecutor.mozSyncExec(new Runnable() { @Override public void run() { synchronized (LimeMozillaDownloadProgressListenerImpl.this) { if (!LimeMozillaDownloadProgressListenerImpl.this.isPaused()) { try { getDownloadManager().pauseDownload(downloadId); } catch (XPCOMException e) { //TODO catching this exception because user in ui can click multiple times and mozilla code cannot handle that //should revisit and figure out a better way of ahndling this issue LOG.debug(e.getMessage(), e); } changeState(nsIDownloadManager.DOWNLOAD_PAUSED); } } } }); } @Override public void removeDownload() { MozillaExecutor.mozSyncExec(new Runnable() { @Override public void run() { synchronized (LimeMozillaDownloadProgressListenerImpl.this) { manager.removeListener(LimeMozillaDownloadProgressListenerImpl.this); if (state.get() != nsIDownloadManager.DOWNLOAD_FINISHED) { // overriding state to canceled so ti will be removed // from the download list, gui code checks for aborted // state. changeState(nsIDownloadManager.DOWNLOAD_CANCELED); } } } }); } @Override public void resumeDownload() { MozillaExecutor.mozSyncExec(new Runnable() { @Override public void run() { synchronized (LimeMozillaDownloadProgressListenerImpl.this) { if (LimeMozillaDownloadProgressListenerImpl.this.isPaused() || LimeMozillaDownloadProgressListenerImpl.this.isQueued()) { try { getDownloadManager().resumeDownload(downloadId); } catch (XPCOMException e) { //TODO catching this exception because user in ui can click multiple times and mozilla code cannot handle that //should revisit and figure out a better way of ahndling this issue LOG.debug(e.getMessage(), e); } } } } }); } private nsIDownloadManager getDownloadManager() { nsIDownloadManager downloadManager = xpComUtility.getServiceProxy( "@mozilla.org/download-manager;1", nsIDownloadManager.class); return downloadManager; } @Override public void addListener(EventListener<DownloadStateEvent> listener) { listeners.addListener(listener); } @Override public boolean removeListener(EventListener<DownloadStateEvent> listener) { return listeners.removeListener(listener); } @Override public synchronized boolean isQueued() { boolean queued = state.get() == nsIDownloadManager.DOWNLOAD_QUEUED; return queued; } @Override public synchronized boolean isCancelled() { DownloadState downloadStatus = getDownloadStatus(); boolean cancelled = downloadStatus == DownloadState.ABORTED || downloadStatus == DownloadState.INVALID; return cancelled; } @Override public synchronized void setDiskError() { state.set(nsIDownloadManager.DOWNLOAD_DIRTY); } }