/**
* Copyright (c) Codice Foundation
* <p/>
* This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser
* General Public License as published by the Free Software Foundation, either version 3 of the
* License, or any later version.
* <p/>
* 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
* Lesser General Public License for more details. A copy of the GNU Lesser General Public License
* is distributed along with this program and can be found at
* <http://www.gnu.org/licenses/lgpl.html>.
*/
package ddf.catalog.resource.download;
import java.util.TimerTask;
import java.util.concurrent.Future;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ddf.catalog.data.Metacard;
import ddf.catalog.event.retrievestatus.DownloadsStatusEventPublisher;
import ddf.catalog.operation.ResourceResponse;
/**
* Monitors the @ReliableResourceCallable, detecting if no bytes have been read from the resource's @InputStream
* for the monitor's period. If this is detected, then this monitor is canceled, the @ReliableResourceCallable's
* interrupt flag is set, and the @Future that started it is canceled.
*
*/
public class ResourceRetrievalMonitor extends TimerTask {
private static final Logger LOGGER = LoggerFactory.getLogger(ResourceRetrievalMonitor.class);
private long previousBytesRead = 0;
private final Future<?> future;
private final ReliableResourceCallable reliableResourceCallable;
private final long monitorPeriod;
private final DownloadsStatusEventPublisher eventPublisher;
private final ResourceResponse resourceResponse;
private final Metacard metacard;
private final String downloadIdentifier;
/**
* @param future the @Future that started the @ReliableResourceCallable doing the resource download
* @param reliableResourceCallable the @Callable to interrupt if no bytes read in specified period
* @param monitorPeriod the frequency (in ms) this monitor should check for bytes read
* @param eventPublisher reference to the publisher of status events as the download progresses
* @param resourceResponse the resource response of the request
* @param metacard the @Metacard associated with the resource being downloaded
*/
public ResourceRetrievalMonitor(Future<?> future,
ReliableResourceCallable reliableResourceCallable, long monitorPeriod,
DownloadsStatusEventPublisher eventPublisher, ResourceResponse resourceResponse,
Metacard metacard, String downloadIdentifier) {
this.future = future;
this.reliableResourceCallable = reliableResourceCallable;
this.monitorPeriod = monitorPeriod;
this.eventPublisher = eventPublisher;
this.resourceResponse = resourceResponse;
this.metacard = metacard;
this.downloadIdentifier = downloadIdentifier;
}
/**
* Returns the number of bytes read from the resource's @InputStream thus far.
*
* @return
*/
public long getBytesRead() {
return previousBytesRead;
}
@Override
public void run() {
long bytesRead = reliableResourceCallable.getBytesRead();
long chunkByteCount = bytesRead - previousBytesRead;
if (chunkByteCount > 0) {
long transferSpeed = (chunkByteCount / monitorPeriod) * 1000; // in bytes per second
LOGGER.debug(
"Downloaded {} bytes in last {} ms. Total bytes read = {}, transfer speed = {}/second",
chunkByteCount, monitorPeriod, bytesRead,
FileUtils.byteCountToDisplaySize(transferSpeed));
previousBytesRead = reliableResourceCallable.getBytesRead();
if (null != eventPublisher) {
eventPublisher.postRetrievalStatus(resourceResponse,
DownloadsStatusEventPublisher.ProductRetrievalStatus.IN_PROGRESS, metacard,
null, bytesRead, downloadIdentifier);
} else {
LOGGER.debug("Event publisher is null ");
}
} else {
LOGGER.debug(
"No bytes downloaded in last {} ms - cancelling ResourceRetrievalMonitor and ReliableResourceCallable future (thread).",
monitorPeriod);
// Stop this ResourceRetrievalMonitor since the ReliableResourceCallable being watched will be stopped now
cancel();
// Stop the download thread
// synchronized so that Callable can finish any writing to OutputStreams before being canceled
synchronized (reliableResourceCallable) {
LOGGER.debug("Setting interruptCaching on ReliableResourceCallable thread");
reliableResourceCallable.setInterruptDownload(true);
// Without this the Future that is running the ReliableResourceCallable will not stop
boolean status = future.cancel(true);
LOGGER.debug("future cancelling status = {}", status);
}
}
}
}