/**
* Copyright 2012-2013 Maciej Jaworski, Mariusz Kapcia, Paweł Kędzia, Mateusz Kubuszok
*
* <p>Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at</p>
*
* <p>http://www.apache.org/licenses/LICENSE-2.0</p>
*
* <p>Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.</p>
*/
package com.autoupdater.client.download.services;
import static java.lang.Thread.State.NEW;
import static net.jsdpu.logger.Logger.getLogger;
import java.net.HttpURLConnection;
import java.util.HashSet;
import java.util.Set;
import net.jsdpu.logger.Logger;
import com.autoupdater.client.download.DownloadResultException;
import com.autoupdater.client.download.DownloadServiceMessage;
import com.autoupdater.client.download.DownloadServiceProgressMessage;
import com.autoupdater.client.download.EDownloadStatus;
import com.autoupdater.client.download.events.IDownloadListener;
import com.autoupdater.client.download.runnables.AbstractDownloadRunnable;
import com.autoupdater.client.utils.executions.ExecutionWithErrors;
import com.autoupdater.client.utils.services.IObserver;
import com.autoupdater.client.utils.services.ObservableService;
import com.google.common.base.Objects;
/**
* Superclass of all DownloadServices.
*
* <p>
* Results can be obtained by getResult() as soon as state (getState()) changes
* to PROCESSED.
* </p>
*
* <p>
* Current state of download can be monitored, since DownloadService is
* ObservableService. Messages are passed as DownloadServiceMessages. If
* download state is IN_PROGRESS, massages are passed as
* DownloadServiceProgressMessages.
* </p>
*
* <p>
* If download fails or is cancelled it's state is changed to FAILED or
* CANCELLED respectively.
* </p>
*
* @see com.autoupdater.client.download.services.PackagesInfoDownloadService
* @see com.autoupdater.client.download.services.UpdateInfoDownloadService
* @see com.autoupdater.client.download.services.ChangelogInfoDownloadService
* @see com.autoupdater.client.download.services.FileDownloadService
* @see com.autoupdater.client.download.DownloadServiceMessage
* @see com.autoupdater.client.download.DownloadServiceProgressMessage
*
* @param <Result>
* type of result returned by download service
*/
public abstract class AbstractDownloadService<Result> extends
ObservableService<DownloadServiceMessage> implements IObserver<DownloadServiceMessage>,
ExecutionWithErrors {
private static final Logger logger = getLogger(AbstractDownloadService.class);
private Set<IDownloadListener> listeners;
private AbstractDownloadRunnable<Result> runnable;
private Thread downloadThread;
private HttpURLConnection connection;
private String fileDestinationPath;
/**
* Creates instance of AbstractDownloadService.
*
* <p>
* Used by some implementations.
* <p>
*
* @see com.autoupdater.client.download.services.PackagesInfoDownloadService
* @see com.autoupdater.client.download.services.UpdateInfoDownloadService
* @see com.autoupdater.client.download.services.ChangelogInfoDownloadService
*
* @param connection
* connection used for obtain data
*/
public AbstractDownloadService(HttpURLConnection connection) {
this(connection, null);
}
/**
* Creates instance of AbstractDownloadService.
*
* <p>
* Used by some implementations.
* <p>
*
* @see com.autoupdater.client.download.services.FileDownloadService
*
* @param connection
* connection used for obtain data
* @param fileDestinationPath
* path to file where result should be stored
*/
public AbstractDownloadService(HttpURLConnection connection, String fileDestinationPath) {
this.connection = connection;
this.fileDestinationPath = fileDestinationPath;
runnable = getRunnable();
runnable.addObserver(this);
listeners = new HashSet<IDownloadListener>();
downloadThread = new Thread(runnable);
}
/**
* Starts download thread.
*/
public synchronized void start() {
downloadThread.start();
logger.debug("Ordered start of download from: " + getConnection().getURL());
}
/**
* Checks whether or not thread has already started.
*
* @return whether or not thread has started
*/
public boolean hasStarted() {
return !downloadThread.getState().equals(NEW);
}
/**
* Cancels download.
*/
public void cancel() {
downloadThread.interrupt();
logger.debug("Ordered cancellation of download from: " + getConnection().getURL());
}
/**
* Obtains content length.
*
* @see com.autoupdater.client.download.runnables.AbstractDownloadRunnable#getContentLength()
*
* @return content's length in bytes if value available, -1 otherwise
*/
public long getContentLength() {
return runnable.getContentLength();
}
/**
* Returns connection used by service.
*
* @return connection used by service
*/
public HttpURLConnection getConnection() {
return connection;
}
/**
* Returns file destination path if available.
*
* @return file destination path if available, null otherwise
*/
public String getFileDestinationPath() {
return fileDestinationPath;
}
/**
* Obtains result of download if available.
*
* @return result of download
* @throws DownloadResultException
* thrown if download is incorrect state, in particular, if it
* wasn't finished or was cancelled
*/
public Result getResult() throws DownloadResultException {
return runnable.getResult();
}
/**
* Returns status of download.
*
* @see com.autoupdater.client.download.EDownloadStatus
*
* @return state of download
*/
public EDownloadStatus getStatus() {
return runnable.getStatus();
}
@Override
public Throwable getThrownException() {
return runnable.getThrownException();
}
@Override
public void setThrownException(Throwable throwable) {
runnable.setThrownException(throwable);
}
@Override
public void throwExceptionIfErrorOccured() throws Throwable {
runnable.throwExceptionIfErrorOccured();
}
/**
* Makes current thread wait for download thread to finish.
*
* @throws InterruptedException
* thrown if thread was interrupted, e.g. when it was cancelled
*/
public void joinThread() throws InterruptedException {
downloadThread.join();
}
/**
* Adds listener to set of subscribers.
*
* @param listener
* download listener
*/
public void addListener(IDownloadListener listener) {
listeners.add(listener);
}
/**
* Removes listener from set of subscribers.
*
* @param listener
* download listener
*/
public void removeListener(IDownloadListener listener) {
listeners.remove(listener);
}
@Override
public void update(ObservableService<DownloadServiceMessage> observable,
DownloadServiceMessage message) {
if (Objects.equal(observable, runnable)) {
hasChanged();
notifyObservers(message);
notifyListeners(message);
}
}
/**
* Returns runnable instance. Used for object initialization.
*
* @return DownloadService instance
*/
protected abstract AbstractDownloadRunnable<Result> getRunnable();
/**
* Notifies all listeners about changes.
*
* @param message
* message that should be processed for listeners
*/
private void notifyListeners(DownloadServiceMessage message) {
DownloadEventImpl event = null;
if (message.getProgressMessage() != null) {
DownloadServiceProgressMessage progressMessage = message.getProgressMessage();
double progress = ((double) progressMessage.getCurrentAmount() / (double) progressMessage
.getOverallAmount());
event = new DownloadEventImpl(this, message.getMessage(), progress);
} else
event = new DownloadEventImpl(this, message.getMessage());
switch (getStatus()) {
default:
case HASNT_STARTED:
break;
case CONNECTED:
for (IDownloadListener listener : listeners)
listener.downloadStarted(event);
break;
case IN_PROCESS:
for (IDownloadListener listener : listeners)
listener.downloadInProgress(event);
break;
case COMPLETED:
for (IDownloadListener listener : listeners)
listener.downloadCompleted(event);
break;
case PROCESSED:
for (IDownloadListener listener : listeners) {
listener.downloadProcessed(event);
listener.downloadFinished(event);
}
break;
case FAILED:
for (IDownloadListener listener : listeners) {
listener.downloadFailed(event);
listener.downloadFinished(event);
}
break;
case CANCELLED:
for (IDownloadListener listener : listeners) {
listener.downloadCancelled(event);
listener.downloadFinished(event);
}
break;
}
}
}