package games.strategy.engine.framework.map.download;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import com.google.common.io.Files;
import games.strategy.debug.ClientLogger;
import games.strategy.engine.ClientFileSystemHelper;
/**
* Keeps track of the state for a file download from a URL.
* This class notifies listeners as appropriate while download state changes.
* A 'DownloadStrategy' does the heavy lifting URL download work.
*
* @see MapDownloadStrategy
*/
public class DownloadFile {
public enum DownloadState {
NOT_STARTED, DOWNLOADING, CANCELLED, DONE
}
private final List<Runnable> downloadCompletedListeners;
private final Consumer<Integer> progressUpdateListener;
private final DownloadFileDescription downloadDescription;
private volatile DownloadState state = DownloadState.NOT_STARTED;
/**
* Creates a new FileDownload object.
* Does not actually start the download, call 'startAsyncDownload()' to start the download.
*
* @param download The details of what to download
* @param progressUpdateListener Called periodically while download progress is made.
* @param completionListener Called when the File download is complete.
*/
public DownloadFile(final DownloadFileDescription download, final Consumer<Integer> progressUpdateListener,
final Runnable completionListener) {
this(download, progressUpdateListener);
this.addDownloadCompletedListener(completionListener);
}
protected DownloadFile(final DownloadFileDescription download, final Consumer<Integer> progressUpdateListener) {
this.downloadDescription = download;
this.progressUpdateListener = progressUpdateListener;
this.downloadCompletedListeners = new ArrayList<>();
}
public void startAsyncDownload() {
final File fileToDownloadTo = ClientFileSystemHelper.createTempFile();
final FileSizeWatcher watcher = new FileSizeWatcher(fileToDownloadTo, progressUpdateListener);
addDownloadCompletedListener(() -> watcher.stop());
state = DownloadState.DOWNLOADING;
createDownloadThread(fileToDownloadTo).start();
}
/*
* Creates a thread that will download to a target temporary file, and once
* complete and if the download state is not cancelled, it will then move
* the completed download temp file to: 'downloadDescription.getInstallLocation()'
*/
private Thread createDownloadThread(final File fileToDownloadTo) {
return new Thread(() -> {
if (state != DownloadState.CANCELLED) {
final URL url = downloadDescription.newURL();
try {
DownloadUtils.downloadFile(url, fileToDownloadTo);
} catch (final Exception e) {
ClientLogger.logError("Failed to download: " + url, e);
}
if (state == DownloadState.CANCELLED) {
return;
}
state = DownloadState.DONE;
try {
Files.move(fileToDownloadTo, downloadDescription.getInstallLocation());
final DownloadFileProperties props = new DownloadFileProperties();
props.setFrom(downloadDescription);
DownloadFileProperties.saveForZip(downloadDescription.getInstallLocation(), props);
} catch (final Exception e) {
final String msg = "Failed to move downloaded file (" + fileToDownloadTo.getAbsolutePath() + ") to: "
+ downloadDescription.getInstallLocation().getAbsolutePath();
ClientLogger.logError(msg, e);
}
}
// notify listeners we finished the download
downloadCompletedListeners.forEach(e -> e.run());
});
}
protected DownloadState getDownloadState() {
return state;
}
public void cancelDownload() {
if (!isDone()) {
state = DownloadState.CANCELLED;
}
}
public boolean isDone() {
return state == DownloadState.CANCELLED || state == DownloadState.DONE;
}
public boolean isInProgress() {
return state == DownloadState.DOWNLOADING;
}
public boolean isWaiting() {
return state == DownloadState.NOT_STARTED;
}
public void addDownloadCompletedListener(final Runnable listener) {
downloadCompletedListeners.add(listener);
}
}