From 9ef888c63454dbef1f754d2fd189f694aa11f7aa Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sun, 17 Feb 2019 15:54:52 +0400 Subject: [PATCH] UI: symbols download fixes: * fixed that mana symbols don't refresh after download finisged (#5592, no more xmage restarts); * fixed that window don't close after download finished; --- .../org/mage/plugins/card/CardPluginImpl.java | 120 ++++++++------ .../org/mage/plugins/card/dl/DownloadGui.java | 152 ++++++++---------- .../org/mage/plugins/card/dl/DownloadJob.java | 31 ++-- .../org/mage/plugins/card/dl/Downloader.java | 111 +++++++------ .../card/dl/lm/AbstractLaternaBean.java | 10 +- 5 files changed, 223 insertions(+), 201 deletions(-) diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/CardPluginImpl.java b/Mage.Client/src/main/java/org/mage/plugins/card/CardPluginImpl.java index c8a403dc3d..4e690bf1a5 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/CardPluginImpl.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/CardPluginImpl.java @@ -1,26 +1,9 @@ package org.mage.plugins.card; -import java.awt.BorderLayout; -import java.awt.Dimension; -import java.awt.Frame; -import java.awt.Rectangle; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.awt.image.BufferedImage; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.TimeUnit; -import javax.swing.JComponent; -import javax.swing.JDialog; -import javax.swing.JLayeredPane; import mage.cards.MagePermanent; import mage.cards.action.ActionCallback; import mage.client.dialog.PreferencesDialog; import mage.client.util.GUISizeHelper; -import mage.constants.Rarity; import mage.interfaces.plugin.CardPlugin; import mage.view.CardView; import mage.view.CounterView; @@ -41,14 +24,21 @@ import org.mage.plugins.card.dl.sources.ScryfallSymbolsSource; import org.mage.plugins.card.images.ImageCache; import org.mage.plugins.card.info.CardInfoPaneImpl; +import javax.swing.*; +import java.awt.*; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.image.BufferedImage; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.List; +import java.util.*; +import java.util.concurrent.TimeUnit; + /** * {@link CardPlugin} implementation. * - * @author nantuko - * @version 0.1 01.11.2010 Mage permanents. Sorting card layout. - * @version 0.6 17.07.2011 #sortPermanents got option to display non-land - * permanents in one pile - * @version 0.7 29.07.2011 face down cards support + * @author nantuko, JayDi85 */ @PluginImplementation @Author(name = "nantuko") @@ -584,6 +574,10 @@ public class CardPluginImpl implements CardPlugin { } } + private void symbolsOnFinish() { + + } + /** * Download various symbols (mana, tap, set). * @@ -591,23 +585,25 @@ public class CardPluginImpl implements CardPlugin { */ @Override public void downloadSymbols(String imagesDir) { - final DownloadGui g = new DownloadGui(new Downloader()); + final Downloader downloader = new Downloader(); + final DownloadGui downloadGui = new DownloadGui(downloader); - Iterable it; + LOGGER.info("Symbols download prepare..."); + Iterable jobs; - it = new GathererSymbols(); - for (DownloadJob job : it) { - g.getDownloader().add(job); + jobs = new GathererSymbols(); + for (DownloadJob job : jobs) { + downloader.add(job); } - it = new GathererSets(); - for (DownloadJob job : it) { - g.getDownloader().add(job); + jobs = new GathererSets(); + for (DownloadJob job : jobs) { + downloader.add(job); } - it = new ScryfallSymbolsSource(); - for (DownloadJob job : it) { - g.getDownloader().add(job); + jobs = new ScryfallSymbolsSource(); + for (DownloadJob job : jobs) { + downloader.add(job); } /* @@ -615,26 +611,56 @@ public class CardPluginImpl implements CardPlugin { for (DownloadJob job : it) { g.getDownloader().add(job); } - */ - it = new DirectLinksForDownload(); - for (DownloadJob job : it) { - g.getDownloader().add(job); + */ + + jobs = new DirectLinksForDownload(); + for (DownloadJob job : jobs) { + downloader.add(job); } - JDialog d = new JDialog((Frame) null, "Download symbols", false); - d.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); - d.addWindowListener(new WindowAdapter() { + LOGGER.info("Symbols download needs " + downloader.getJobs().size() + " files"); + + // download GUI dialog + JDialog dialog = new JDialog((Frame) null, "Download symbols", false); + dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); + dialog.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { - g.getDownloader().dispose(); - ManaSymbols.loadImages(); - // TODO: check reload process after download (icons do not update) + // user force to close window/downloader + downloader.cleanup(); } }); - d.setLayout(new BorderLayout()); - d.add(g); - d.pack(); - d.setVisible(true); + dialog.setLayout(new BorderLayout()); + dialog.add(downloadGui); + dialog.pack(); + dialog.setVisible(true); + + // downloader controller thread + SwingWorker worker = new SwingWorker() { + @Override + protected Void doInBackground() throws Exception { + downloader.publishAllJobs(); + downloader.waitFinished(); + downloader.cleanup(); + return null; + } + }; + // downloader finisher + worker.addPropertyChangeListener(new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName().equals("state")) { + if (evt.getNewValue() == SwingWorker.StateValue.DONE) { + // all done, can close dialog and refresh symbols for UI + LOGGER.info("Symbols download finished"); + dialog.dispose(); + ManaSymbols.loadImages(); + ImageCache.clearCache(); + } + } + } + }); + worker.execute(); } @Override diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/DownloadGui.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/DownloadGui.java index e3ff2ca179..3ae0601657 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/DownloadGui.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/DownloadGui.java @@ -1,113 +1,105 @@ -/** - * DownloadGui.java - * - * Created on 25.08.2010 - */ - package org.mage.plugins.card.dl; +import org.mage.plugins.card.dl.DownloadJob.State; -import java.awt.BorderLayout; -import java.awt.Dimension; +import javax.swing.*; +import java.awt.*; import java.beans.IndexedPropertyChangeEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.HashMap; import java.util.Map; -import javax.swing.BorderFactory; -import javax.swing.BoundedRangeModel; -import javax.swing.BoxLayout; -import javax.swing.DefaultBoundedRangeModel; -import javax.swing.JButton; -import javax.swing.JPanel; -import javax.swing.JProgressBar; -import javax.swing.JScrollPane; - -import org.mage.plugins.card.dl.DownloadJob.State; - /** - * The class DownloadGui. - * - * @version V0.0 25.08.2010 + * Downloader GUI to control and show progress + * * @author Clemens Koza */ public class DownloadGui extends JPanel { - private static final long serialVersionUID = -7346572382493844327L; + private static final long serialVersionUID = -7346572382493844327L; - private final Downloader d; - private final DownloadListener l = new DownloadListener(); - private final BoundedRangeModel model = new DefaultBoundedRangeModel(0, 0, 0, 0); - private final JProgressBar progress = new JProgressBar(model); + private final Downloader downloader; + private final DownloadListener listener = new DownloadListener(); + private final BoundedRangeModel progressModel = new DefaultBoundedRangeModel(0, 0, 0, 0); + private final JProgressBar progressBar = new JProgressBar(progressModel); - private final Map progresses = new HashMap<>(); - private final JPanel panel = new JPanel(); + private final Map jobPanels = new HashMap<>(); + private final JPanel basicPanel = new JPanel(); public DownloadGui(Downloader downloader) { super(new BorderLayout()); - this.d = downloader; - downloader.addPropertyChangeListener(l); + this.downloader = downloader; + downloader.addPropertyChangeListener(listener); JPanel p = new JPanel(new BorderLayout()); p.setBorder(BorderFactory.createTitledBorder("Progress:")); - p.add(progress); - JButton b = new JButton("X"); - b.addActionListener(e -> { - d.dispose(); + p.add(progressBar); + JButton closeButton = new JButton("X"); + closeButton.addActionListener(e -> { + this.downloader.cleanup(); }); - p.add(b, BorderLayout.EAST); + p.add(closeButton, BorderLayout.EAST); add(p, BorderLayout.NORTH); - panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); - JScrollPane pane = new JScrollPane(panel); + basicPanel.setLayout(new BoxLayout(basicPanel, BoxLayout.Y_AXIS)); + JScrollPane pane = new JScrollPane(basicPanel); pane.setPreferredSize(new Dimension(500, 300)); add(pane); - for(int i = 0; i < downloader.getJobs().size(); i++) { + for (int i = 0; i < downloader.getJobs().size(); i++) { addJob(i, downloader.getJobs().get(i)); } } public Downloader getDownloader() { - return d; + return downloader; } private class DownloadListener implements PropertyChangeListener { + @Override public void propertyChange(PropertyChangeEvent evt) { String name = evt.getPropertyName(); - if(evt.getSource() instanceof DownloadJob) { - DownloadPanel p = progresses.get(evt.getSource()); + if (evt.getSource() instanceof DownloadJob) { + // one job changes + DownloadPanel panel = jobPanels.get(evt.getSource()); switch (name) { case "state": - if(evt.getOldValue() == State.FINISHED || evt.getOldValue() == State.ABORTED) { + if (evt.getOldValue() == State.FINISHED || evt.getOldValue() == State.ABORTED) { + // started changeProgress(-1, 0); - } if(evt.getNewValue() == State.FINISHED || evt.getOldValue() == State.ABORTED) { - changeProgress(+1, 0); - } if(p != null) { - p.setVisible(p.getJob().getState() != State.FINISHED); - p.revalidate(); - } break; + } + if (evt.getNewValue() == State.FINISHED || evt.getOldValue() == State.ABORTED) { + // finished + changeProgress(+1, 0); + } + if (panel != null) { + panel.setVisible(panel.getJob().getState() != State.FINISHED); + panel.revalidate(); + } + break; case "message": - if(p != null) { - JProgressBar bar = p.getBar(); - String message = p.getJob().getMessage(); + if (panel != null) { + JProgressBar bar = panel.getBar(); + String message = panel.getJob().getMessage(); bar.setStringPainted(message != null); bar.setString(message); - } break; + } + break; } - } else if(evt.getSource() == d) { - if("jobs".equals(name)) { + } else if (evt.getSource() == downloader) { + // all jobs changes (add/delete) + if ("jobs".equals(name)) { IndexedPropertyChangeEvent ev = (IndexedPropertyChangeEvent) evt; int index = ev.getIndex(); DownloadJob oldValue = (DownloadJob) ev.getOldValue(); - if(oldValue != null) { + if (oldValue != null) { removeJob(index, oldValue); } DownloadJob newValue = (DownloadJob) ev.getNewValue(); - if(newValue != null) { + if (newValue != null) { addJob(index, newValue); } } @@ -116,39 +108,39 @@ public class DownloadGui extends JPanel { } private synchronized void addJob(int index, DownloadJob job) { - job.addPropertyChangeListener(l); + job.addPropertyChangeListener(listener); changeProgress(0, +1); DownloadPanel p = new DownloadPanel(job); - progresses.put(job, p); - panel.add(p, index); - panel.revalidate(); + jobPanels.put(job, p); + basicPanel.add(p, index); + basicPanel.revalidate(); } private synchronized void removeJob(int index, DownloadJob job) { - assert progresses.get(job) == panel.getComponent(index); - job.removePropertyChangeListener(l); + assert jobPanels.get(job) == basicPanel.getComponent(index); + job.removePropertyChangeListener(listener); changeProgress(0, -1); - progresses.remove(job); - panel.remove(index); - panel.revalidate(); + jobPanels.remove(job); + basicPanel.remove(index); + basicPanel.revalidate(); } private synchronized void changeProgress(int progress, int total) { - progress += model.getValue(); - total += model.getMaximum(); - model.setMaximum(total); - model.setValue(progress); - this.progress.setStringPainted(true); - this.progress.setString(progress + "/" + total); + progress += progressModel.getValue(); + total += progressModel.getMaximum(); + progressModel.setMaximum(total); + progressModel.setValue(progress); + this.progressBar.setStringPainted(true); + this.progressBar.setString(progress + "/" + total); } private class DownloadPanel extends JPanel { private static final long serialVersionUID = 1187986738303477168L; - private final DownloadJob job; - private final JProgressBar bar; + private final DownloadJob job; + private final JProgressBar bar; - public DownloadPanel(DownloadJob job) { + DownloadPanel(DownloadJob job) { super(new BorderLayout()); this.job = job; @@ -156,7 +148,7 @@ public class DownloadGui extends JPanel { add(bar = new JProgressBar(job.getProgress())); JButton b = new JButton("X"); b.addActionListener(e -> { - switch(this.job.getState()) { + switch (this.job.getState()) { case NEW: case PREPARING: case WORKING: @@ -165,7 +157,7 @@ public class DownloadGui extends JPanel { }); add(b, BorderLayout.EAST); - if(job.getState() == State.FINISHED | job.getState() == State.ABORTED) { + if (job.getState() == State.FINISHED | job.getState() == State.ABORTED) { changeProgress(+1, 0); } setVisible(job.getState() != State.FINISHED); @@ -177,15 +169,13 @@ public class DownloadGui extends JPanel { Dimension d = getPreferredSize(); d.width = Integer.MAX_VALUE; setMaximumSize(d); -// d.width = 500; -// setMinimumSize(d); } - public DownloadJob getJob() { + DownloadJob getJob() { return job; } - public JProgressBar getBar() { + JProgressBar getBar() { return bar; } } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/DownloadJob.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/DownloadJob.java index c39fb6d163..72c99422ab 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/DownloadJob.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/DownloadJob.java @@ -1,28 +1,18 @@ -/** - * DownloadJob.java - * - * Created on 25.08.2010 - */ package org.mage.plugins.card.dl; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.Proxy; -import java.net.URL; -import java.net.URLConnection; -import javax.swing.BoundedRangeModel; -import javax.swing.DefaultBoundedRangeModel; import org.mage.plugins.card.dl.beans.properties.Property; import org.mage.plugins.card.dl.lm.AbstractLaternaBean; import org.mage.plugins.card.utils.CardImageUtils; +import javax.swing.*; +import java.io.*; +import java.net.Proxy; +import java.net.URL; +import java.net.URLConnection; + /** - * The class DownloadJob. + * Downloader job to download one resource * - * @version V0.0 25.08.2010 * @author Clemens Koza, JayDi85 */ public class DownloadJob extends AbstractLaternaBean { @@ -88,11 +78,8 @@ public class DownloadJob extends AbstractLaternaBean { */ public void setError(String message, Exception error) { if (message == null) { - - message = "Download of " + name + "from " + source.toString() + " caused error: " + error.toString(); + message = "Download of " + name + " from " + source.toString() + " caused error: " + error.toString(); } -// log.warn(message, error); - log.warn(message); this.state.setValue(State.ABORTED); this.error.setValue(error); this.message.setValue(message); @@ -116,7 +103,7 @@ public class DownloadJob extends AbstractLaternaBean { return; } - // change to working state on good prepare call + // can continue this.state.setValue(State.WORKING); } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/Downloader.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/Downloader.java index 497ee62d62..815083b901 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/Downloader.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/Downloader.java @@ -1,26 +1,9 @@ -/** - * Downloader.java - * - * Created on 25.08.2010 - */ package org.mage.plugins.card.dl; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.ConnectException; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import javax.swing.BoundedRangeModel; import org.apache.log4j.Logger; import org.jetlang.channels.Channel; import org.jetlang.channels.MemoryChannel; import org.jetlang.core.Callback; -import org.jetlang.core.Disposable; import org.jetlang.fibers.Fiber; import org.jetlang.fibers.PoolFiberFactory; import org.mage.plugins.card.dl.DownloadJob.Destination; @@ -28,41 +11,56 @@ import org.mage.plugins.card.dl.DownloadJob.Source; import org.mage.plugins.card.dl.DownloadJob.State; import org.mage.plugins.card.dl.lm.AbstractLaternaBean; +import javax.swing.*; +import java.io.*; +import java.net.ConnectException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + /** - * The class Downloader. + * Downloader * - * @version V0.0 25.08.2010 - * @author Clemens Koza + * @author Clemens Koza, JayDi85 */ -public class Downloader extends AbstractLaternaBean implements Disposable { +public class Downloader extends AbstractLaternaBean { private static final Logger logger = Logger.getLogger(Downloader.class); private final List jobs = properties.list("jobs"); - private final Channel channel = new MemoryChannel<>(); + private final Channel jobsQueue = new MemoryChannel<>(); + private CountDownLatch worksCount = null; private final ExecutorService pool = Executors.newCachedThreadPool(); private final List fibers = new ArrayList<>(); public Downloader() { + // prepare 10 threads and start to waiting new download jobs from queue PoolFiberFactory f = new PoolFiberFactory(pool); - //subscribe multiple fibers for parallel execution for (int i = 0, numThreads = 10; i < numThreads; i++) { Fiber fiber = f.create(); fiber.start(); fibers.add(fiber); - channel.subscribe(fiber, new DownloadCallback()); + jobsQueue.subscribe(fiber, new DownloadCallback()); } } - @Override - public void dispose() { + public void cleanup() { + // close all threads and jobs for (DownloadJob j : jobs) { switch (j.getState()) { case NEW: case PREPARING: case WORKING: j.setState(State.ABORTED); + break; + case ABORTED: + case FINISHED: + // don't change state + break; } } @@ -70,16 +68,10 @@ public class Downloader extends AbstractLaternaBean implements Disposable { f.dispose(); } pool.shutdown(); - } - /** - * - * @throws Throwable - */ - @Override - protected void finalize() throws Throwable { - dispose(); - super.finalize(); + while (worksCount.getCount() != 0) { + worksCount.countDown(); + } } public void add(DownloadJob job) { @@ -94,7 +86,27 @@ public class Downloader extends AbstractLaternaBean implements Disposable { } job.setState(State.NEW); jobs.add(job); - channel.publish(job); + } + + public void publishAllJobs() { + worksCount = new CountDownLatch(jobs.size()); + for (DownloadJob job : jobs) { + jobsQueue.publish(job); + } + } + + public void waitFinished() { + try { + while (worksCount.getCount() != 0) { + worksCount.await(60, TimeUnit.SECONDS); + + if (worksCount.getCount() != 0) { + logger.warn("Symbols download too long..."); + } + } + } catch (InterruptedException e) { + logger.error("Need to stop symbols download..."); + } } public List getJobs() { @@ -111,20 +123,23 @@ public class Downloader extends AbstractLaternaBean implements Disposable { @Override public void onMessage(DownloadJob job) { - // start to work - // the job won't be processed by multiple threads + // each 10 threads gets same jobs, but take to work only one NEW synchronized (job) { - if (job.getState() != State.NEW) { - return; - } - - job.doPrepareAndStartWork(); - - if (job.getState() != State.WORKING) { + if (job.getState() == State.NEW) { + // take new job + job.doPrepareAndStartWork(); + if (job.getState() != State.WORKING) { + logger.warn("Can't prepare symbols download job: " + job.getName()); + worksCount.countDown(); + return; + } + } else { + // skip job (other thread takes it) return; } } + // real work for new job // download and save data try { Source src = job.getSource(); @@ -132,9 +147,11 @@ public class Downloader extends AbstractLaternaBean implements Disposable { BoundedRangeModel progress = job.getProgress(); if (dst.isValid()) { + // already done progress.setMaximum(1); progress.setValue(1); } else { + // downloading if (dst.exists()) { try { dst.delete(); @@ -149,7 +166,7 @@ public class Downloader extends AbstractLaternaBean implements Disposable { try { byte[] buf = new byte[8 * 1024]; int total = 0; - for (int len; (len = is.read(buf)) != -1;) { + for (int len; (len = is.read(buf)) != -1; ) { if (job.getState() == State.ABORTED) { throw new IOException("Job was aborted"); } @@ -193,6 +210,8 @@ public class Downloader extends AbstractLaternaBean implements Disposable { logger.warn("Error resource download " + job.getName() + " from " + job.getSource().toString() + ": " + message); } catch (IOException ex) { job.setError(ex); + } finally { + worksCount.countDown(); } } } diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/lm/AbstractLaternaBean.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/lm/AbstractLaternaBean.java index be462e1829..224a1b036e 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/lm/AbstractLaternaBean.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/lm/AbstractLaternaBean.java @@ -1,6 +1,6 @@ /** * AbstractLaternaBean.java - * + *

* Created on 25.08.2010 */ @@ -16,12 +16,12 @@ import org.mage.plugins.card.dl.beans.properties.bound.BoundProperties; /** * The class AbstractLaternaBean. - * - * @version V0.0 25.08.2010 + * * @author Clemens Koza + * @version V0.0 25.08.2010 */ public class AbstractLaternaBean extends AbstractBoundBean { protected static final Logger log = Logger.getLogger(AbstractLaternaBean.class); - protected final Properties properties = new BoundProperties(s); - protected EventListenerList listeners = new EventListenerList(); + protected final Properties properties = new BoundProperties(s); + protected EventListenerList listeners = new EventListenerList(); }