From 9640cd8dcf84f21339dee4105058c3a28676a00a Mon Sep 17 00:00:00 2001 From: Henry Le Grys Date: Mon, 24 May 2021 10:05:15 +0100 Subject: [PATCH] Add progress bars --- .../plugin/curse/creator/CurseModsDialog.java | 55 ++++++++++++++++--- .../plugin/curse/creator/CursePack.java | 16 +++--- 2 files changed, 56 insertions(+), 15 deletions(-) diff --git a/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/creator/CurseModsDialog.java b/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/creator/CurseModsDialog.java index fa9e3cc..264a3a0 100644 --- a/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/creator/CurseModsDialog.java +++ b/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/creator/CurseModsDialog.java @@ -6,6 +6,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.skcraft.concurrency.DefaultProgress; +import com.skcraft.concurrency.ObservableFuture; import com.skcraft.launcher.creator.model.creator.Pack; import com.skcraft.launcher.creator.plugin.MenuContext; import com.skcraft.launcher.creator.plugin.PluginMenu; @@ -28,6 +29,7 @@ import java.awt.event.ActionEvent; import java.awt.image.BufferedImage; import java.io.File; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.function.Consumer; @@ -39,14 +41,17 @@ public class CurseModsDialog extends JDialog { private final CursePack cursePack; private final JPanel panel = new JPanel(new BorderLayout(0, 5)); + private final JLabel title = new JLabel("Search for mods on CurseForge:"); private final JTextField searchBox = new JTextField(); private final JButton searchButton = new JButton("Search"); + private final JProgressBar progressBar = new JProgressBar(0, 1000); private final JList searchPane = new JList<>(); private final JList selectedPane = new JList<>(); private final JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, new JScrollPane(searchPane), new JScrollPane(selectedPane)); private final JButton addMod = new JButton("Add Mods >>"); private final JButton removeMod = new JButton("<< Remove Mods"); private final JButton done = new JButton("Done"); + private Timer activeTimer; private final CurseProjectListRenderer projectRenderer = new CurseProjectListRenderer(); @@ -94,10 +99,17 @@ public class CurseModsDialog extends JDialog { buttonsPanel.addElement(removeMod); buttonsPanel.setAlignmentX(CENTER_ALIGNMENT); - panel.add(searchBarPanel, BorderLayout.NORTH); + LinedBoxPanel topPanel = new LinedBoxPanel(false); + topPanel.addElement(title); + topPanel.addElement(searchBarPanel); + topPanel.addElement(progressBar); + topPanel.setAlignmentX(CENTER_ALIGNMENT); + + panel.add(topPanel, BorderLayout.NORTH); panel.add(Box.createVerticalStrut(5)); panel.add(splitPane, BorderLayout.CENTER); panel.add(buttonsPanel, BorderLayout.SOUTH); + panel.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0)); add(panel); @@ -105,24 +117,41 @@ public class CurseModsDialog extends JDialog { searchButton.addActionListener(e -> search()); addMod.addActionListener(e -> { - ListenableFuture future = executor.submit(cursePack.addMany(searchPane.getSelectedValuesList())); + CursePack.AddModsCall call = cursePack.addMany(searchPane.getSelectedValuesList()); + ListenableFuture future = executor.submit(call); - // TODO: Update a progress bar built in to the dialog. + setProgressItem(new ObservableFuture<>(future, call)); SwingHelper.addErrorDialogCallback(getOwner(), future); }); removeMod.addActionListener(e -> { - ListenableFuture future = executor.submit( - cursePack.removeMany(selectedPane.getSelectedValuesList())); + CursePack.RemoveModsCall call = cursePack.removeMany(selectedPane.getSelectedValuesList()); + ListenableFuture future = executor.submit(call); + setProgressItem(new ObservableFuture<>(future, call)); SwingHelper.addErrorDialogCallback(getOwner(), future); }); done.addActionListener(e -> dispose()); } + private void setProgressItem(ObservableFuture observable) { + if (activeTimer != null) { + activeTimer.stop(); + } + + activeTimer = new Timer(400, e -> progressBar.setValue((int) (1000 * observable.getProgress()))); + activeTimer.setInitialDelay(0); + activeTimer.start(); + + observable.addListener(() -> activeTimer.stop(), SwingExecutor.INSTANCE); + } + @Override public void dispose() { + if (activeTimer != null) { + activeTimer.stop(); + } // Let's make sure the image cache is emptied projectRenderer.clearCache(); super.dispose(); @@ -173,16 +202,24 @@ public class CurseModsDialog extends JDialog { } private static class CurseProjectListRenderer extends JLabel implements ListCellRenderer { - private HashMap cache; + private final HashMap cache; + private final HashSet pending; public CurseProjectListRenderer() { this.cache = new HashMap<>(); + this.pending = new HashSet<>(); } private BufferedImage getCachedIcon(String imgUrl, Runnable cb) { if (!cache.containsKey(imgUrl)) { + if (pending.contains(imgUrl)) { + return null; + } + + pending.add(imgUrl); ImageWorker.downloadImage(imgUrl, img -> { cache.put(imgUrl, img); + pending.remove(imgUrl); cb.run(); }); @@ -261,9 +298,11 @@ public class CurseModsDialog extends JDialog { ListenableFuture future = ctx.getExecutor().submit(() -> { File target = new File(pack.getDirectory(), "cursemods"); - if (target.isDirectory()) { - scanner.walk(target); + if (!target.isDirectory()) { + target.mkdirs(); } + + scanner.walk(target); return null; }); diff --git a/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/creator/CursePack.java b/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/creator/CursePack.java index dd76b66..90e3ba6 100644 --- a/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/creator/CursePack.java +++ b/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/creator/CursePack.java @@ -1,6 +1,8 @@ package com.skcraft.plugin.curse.creator; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.fasterxml.jackson.databind.SerializationFeature; import com.skcraft.concurrency.ProgressObservable; import com.skcraft.concurrency.SettableProgress; import com.skcraft.launcher.creator.model.creator.Pack; @@ -18,13 +20,13 @@ import java.util.concurrent.Callable; public class CursePack { @Getter private final CurseSearchResults searchResults = new CurseSearchResults(); @Getter private final AddedModList modList = new AddedModList(); - private final ObjectMapper mapper; + private final ObjectWriter writer; private final String gameVersion; private final File curseModsDir; public CursePack(ObjectMapper mapper, Pack pack) { - this.mapper = mapper; + this.writer = mapper.writerWithDefaultPrettyPrinter().without(SerializationFeature.WRITE_NULL_MAP_VALUES); this.gameVersion = pack.getCachedConfig().getGameVersion(); this.curseModsDir = new File(pack.getDirectory(), "cursemods"); } @@ -41,7 +43,7 @@ public class CursePack { File target = loadedMod.getDiskLocation(curseModsDir); log.info(String.format("Saving mod %s", target.getName())); - mapper.writeValue(target, loadedMod.getMod()); + writer.writeValue(target, loadedMod.getMod()); } public void removeMod(AddedMod mod) throws IOException { @@ -63,7 +65,7 @@ public class CursePack { } @RequiredArgsConstructor - private class AddModsCall implements Callable, ProgressObservable { + class AddModsCall implements Callable, ProgressObservable { private final List projects; private final SettableProgress progress = new SettableProgress("", -1); @@ -73,7 +75,7 @@ public class CursePack { int total = projects.size(); for (CurseProject project : projects) { - progress.set(String.format("Adding mod %s", project.getName()), 100 * (double) current / total); + progress.set(String.format("Adding mod %s", project.getName()), (double) current / total); addMod(project); current++; @@ -94,7 +96,7 @@ public class CursePack { } @RequiredArgsConstructor - private class RemoveModsCall implements Callable, ProgressObservable { + class RemoveModsCall implements Callable, ProgressObservable { private final List mods; private final SettableProgress progress = new SettableProgress("", -1); @@ -105,7 +107,7 @@ public class CursePack { for (AddedMod mod : mods) { progress.set(String.format("Removing mod %s", mod.getProject().getName()), - 100 * (double) current / total); + (double) current / total); removeMod(mod); current++;