package games.strategy.engine.framework.map.download;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import games.strategy.debug.ClientLogger;
import games.strategy.util.ThreadUtil;
/**
* Class that accepts and queues download requests. Download requests are started in background
* thread, this class ensures N are in progress until all are done.
*/
public class DownloadCoordinator {
private static final int MAX_CONCURRENT_DOWNLOAD = 3;
private final Map<DownloadFile, Runnable> downloadMap = new HashMap<>();
private final Set<DownloadFileDescription> downloadSet = new HashSet<>();
private volatile boolean cancelled = false;
DownloadCoordinator() {
new Thread(() -> {
while (!cancelled) {
try {
startNextDownloads();
// pause for a brief while before the next iteration, helps avoid a Github too many requests error
ThreadUtil.sleep(250);
} catch (final Exception e) {
ClientLogger.logQuietly(e);
throw e;
}
}
}).start();
}
private void startNextDownloads() {
final long downloadingCount = countDownloadsInProgress();
if (downloadMap != null && downloadingCount < MAX_CONCURRENT_DOWNLOAD) {
startNextDownload();
}
}
private long countDownloadsInProgress() {
return count(download -> download.isInProgress());
}
private long count(final Predicate<DownloadFile> filter) {
return downloadMap.keySet().stream().filter(filter).count();
}
private void startNextDownload() {
for (final Map.Entry<DownloadFile, Runnable> download : downloadMap.entrySet()) {
if (download.getKey().isWaiting()) {
new Thread(download.getValue()).start();
download.getKey().startAsyncDownload();
break;
}
}
}
/**
* Queues up a download request, sending notification to a progress listener and a final notification
* to a download complete listener.
*
* @param download A single map download to queue, may or may not be started immediately.
* @param progressUpdateListener A listener for progress updates, the value passed to the progress listener will be
* the size of the downloaded file in bytes.
* @param completionListener A listener that is called when this specific download finishes.
*/
public void accept(final DownloadFileDescription download, final Consumer<Integer> progressUpdateListener,
final Runnable completionListener, final JProgressBar progressBar) {
// To avoid double acceptance, hold a lock while we check the 'downloadSet'
synchronized (this) {
if (downloadSet.contains(download)) {
return;
} else {
downloadSet.add(download);
}
}
DownloadFile downloadFile = new DownloadFile(download, progressUpdateListener, completionListener);
Runnable progressBarStarter = () -> {
final Optional<Integer> length = DownloadUtils.getDownloadLength(download.newURL());
if (length.isPresent()) {
SwingUtilities.invokeLater(() -> {
progressBar.setMinimum(0);
progressBar.setMaximum(length.get());
});
}
};
downloadMap.put(downloadFile, progressBarStarter);
}
/**
* Will prevent any new downloads from starting. Downloads in progress will continue, but they
* are downloaded to a temp file which will not be moved after cancel.
*/
public void cancelDownloads() {
cancelled = true;
for (final DownloadFile download : downloadMap.keySet()) {
download.cancelDownload();
}
}
}