From e69423af27c26b2061bfded10ccc4f673354cd1e Mon Sep 17 00:00:00 2001 From: Christiaan Date: Tue, 27 Mar 2018 17:26:19 +0200 Subject: [PATCH 1/5] performance improvements for startup added option to skip generating small icons that already exist, load symbol images multithreaded --- .../src/main/java/mage/client/MageFrame.java | 9 +++ .../org/mage/card/arcane/ManaSymbols.java | 78 ++++++++++--------- 2 files changed, 50 insertions(+), 37 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/MageFrame.java b/Mage.Client/src/main/java/mage/client/MageFrame.java index 77d7ebf6ef4..0dc296b3fb0 100644 --- a/Mage.Client/src/main/java/mage/client/MageFrame.java +++ b/Mage.Client/src/main/java/mage/client/MageFrame.java @@ -104,6 +104,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { private static final String LITE_MODE_ARG = "-lite"; private static final String GRAY_MODE_ARG = "-gray"; private static final String FILL_SCREEN_ARG = "-fullscreen"; + private static final String SKIP_DONE_SYMBOLS = "-skipDoneSymbols"; private static final String NOT_CONNECTED_TEXT = ""; private static MageFrame instance; @@ -121,6 +122,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { //TODO: make gray theme, implement theme selector in preferences dialog private static boolean grayMode = false; private static boolean fullscreenMode = false; + private static boolean skipSmallSymbolGenerationForExisting = false; private static final Map CHATS = new HashMap<>(); private static final Map GAMES = new HashMap<>(); @@ -152,6 +154,10 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { public static boolean isGray() { return grayMode; } + + public static boolean isSkipSmallSymbolGenerationForExisting() { + return skipSmallSymbolGenerationForExisting; + } @Override public MageVersion getVersion() { @@ -1191,6 +1197,9 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { if (arg.startsWith(FILL_SCREEN_ARG)) { fullscreenMode = true; } + if (arg.startsWith(SKIP_DONE_SYMBOLS)) { + skipSmallSymbolGenerationForExisting = true; + } } if (!liteMode) { final SplashScreen splash = SplashScreen.getSplashScreen(); diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java b/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java index 8b3c5d9589b..b0ba9953f5a 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/ManaSymbols.java @@ -31,10 +31,14 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.StringTokenizer; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Pattern; +import java.util.stream.IntStream; import javax.imageio.ImageIO; import javax.swing.*; import mage.cards.repository.ExpansionRepository; +import mage.client.MageFrame; import mage.client.constants.Constants; import mage.client.constants.Constants.ResourceSetSize; import mage.client.constants.Constants.ResourceSymbolSize; @@ -56,7 +60,7 @@ public final class ManaSymbols { private static final Logger LOGGER = Logger.getLogger(ManaSymbols.class); private static final Map> manaImages = new HashMap<>(); - private static final Map> setImages = new HashMap<>(); + private static final Map> setImages = new ConcurrentHashMap<>(); private static final HashSet onlyMythics = new HashSet<>(); private static final HashSet withoutSymbols = new HashSet<>(); @@ -76,7 +80,7 @@ public final class ManaSymbols { } private static final Map setImagesExist = new HashMap<>(); private static final Pattern REPLACE_SYMBOLS_PATTERN = Pattern.compile("\\{([^}/]*)/?([^}]*)\\}"); - private static String cachedPath; + private static final String[] symbols = new String[]{"0", "1", "10", "11", "12", "15", "16", "2", "3", "4", "5", "6", "7", "8", "9", "B", "BG", "BR", "BP", "2B", "G", "GU", "GW", "GP", "2G", @@ -166,37 +170,39 @@ public final class ManaSymbols { } catch (Exception e) { } } - + // generate small size try { File file = new File(getResourceSetsPath(ResourceSetSize.MEDIUM)); if (!file.exists()) { file.mkdirs(); } - + String pathRoot = getResourceSetsPath(ResourceSetSize.SMALL) + set; for (String code : codes) { - file = new File(getResourceSetsPath(ResourceSetSize.MEDIUM) + set + '-' + code + ".png"); - if (file.exists()) { - continue; - } - file = new File(getResourceSetsPath(ResourceSetSize.MEDIUM) + set + '-' + code + ".jpg"); - Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); - try { - int width = image.getWidth(null); - int height = image.getHeight(null); - if (height > 0) { - int dx = 0; - if (set.equals("M10") || set.equals("M11") || set.equals("M12")) { - dx = 6; - } - Rectangle r = new Rectangle(15 + dx, (int) (height * (15.0f + dx) / width)); - BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); - File newFile = new File(getResourceSetsPath(ResourceSetSize.SMALL) + set + '-' + code + ".png"); - ImageIO.write(resized, "png", newFile); - } - } catch (Exception e) { + File newFile = new File(pathRoot + '-' + code + ".png"); + if(!(MageFrame.isSkipSmallSymbolGenerationForExisting() && newFile.exists())){// skip if option enabled and file already exists + file = new File(getResourceSetsPath(ResourceSetSize.MEDIUM) + set + '-' + code + ".png"); if (file.exists()) { - file.delete(); + continue; + } + file = new File(getResourceSetsPath(ResourceSetSize.MEDIUM) + set + '-' + code + ".jpg"); + Image image = UI.getImageIcon(file.getAbsolutePath()).getImage(); + try { + int width = image.getWidth(null); + int height = image.getHeight(null); + if (height > 0) { + int dx = 0; + if (set.equals("M10") || set.equals("M11") || set.equals("M12")) { + dx = 6; + } + Rectangle r = new Rectangle(15 + dx, (int) (height * (15.0f + dx) / width)); + BufferedImage resized = ImageHelper.getResizedImage(BufferedImageBuilder.bufferImage(image, BufferedImage.TYPE_INT_ARGB), r); + ImageIO.write(resized, "png", newFile); + } + } catch (Exception e) { + if (file.exists()) { + file.delete(); + } } } } @@ -204,7 +210,6 @@ public final class ManaSymbols { } catch (Exception e) { } } - // mark loaded images // TODO: delete that code, images draw-show must dynamicly File file; @@ -225,7 +230,6 @@ public final class ManaSymbols { } public static BufferedImage loadSVG(File svgFile, int resizeToWidth, int resizeToHeight, boolean useShadow) throws IOException { - // debug: disable shadow gen, need to test it useShadow = false; @@ -419,17 +423,17 @@ public final class ManaSymbols { } private static boolean loadSymbolImages(int size) { - // load all symbols to cash + // load all symbols to cache // priority: SVG -> GIF // gif remain for backward compatibility - boolean fileErrors = false; - - HashMap sizedSymbols = new HashMap<>(); - for (String symbol : symbols) { - + //boolean fileErrors = false; + AtomicBoolean fileErrors = new AtomicBoolean(false); + Map sizedSymbols = new ConcurrentHashMap<>(); + IntStream.range(0, symbols.length).parallel().forEach(i-> { + String symbol = symbols[i]; BufferedImage image = null; - File file = null; + File file; // svg file = getSymbolFileNameAsSVG(symbol); @@ -451,13 +455,13 @@ public final class ManaSymbols { if (image != null) { sizedSymbols.put(symbol, image); } else { - fileErrors = true; + fileErrors.set(true); LOGGER.warn("SVG or GIF symbol can't be load: " + symbol); } - } + }); manaImages.put(size, sizedSymbols); - return !fileErrors; + return !fileErrors.get(); } private static void renameSymbols(String path) { From 98c2b171ded7b00840a68dd843add7446f7f57e4 Mon Sep 17 00:00:00 2001 From: Danny Plenge Date: Wed, 28 Mar 2018 13:49:04 +0200 Subject: [PATCH 2/5] Added the URLHandler which makes the URL's in the message of the day clickable. At the moment when there are 2 URL's in 1 message, it only makes the last one clickable. --- .../java/mage/client/table/TablesPanel.java | 7 +- .../java/mage/client/util/URLHandler.java | 119 ++++++++++++++++++ 2 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 Mage.Client/src/main/java/mage/client/util/URLHandler.java diff --git a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java index 5c11b3c6dc9..474e6956bb8 100644 --- a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java +++ b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java @@ -60,6 +60,7 @@ import mage.client.util.ButtonColumn; import mage.client.util.GUISizeHelper; import mage.client.util.IgnoreList; import mage.client.util.MageTableRowSorter; +import mage.client.util.URLHandler; import mage.client.util.gui.GuiDisplayUtil; import mage.client.util.gui.TableUtil; import mage.constants.*; @@ -579,7 +580,7 @@ public class TablesPanel extends javax.swing.JPanel { this.jPanelBottom.setVisible(false); } else { this.jPanelBottom.setVisible(true); - this.jLabelFooterText.setText(serverMessages.get(0)); + URLHandler.handleMessage(serverMessages.get(0), this.jLabelFooterText); this.jButtonFooterNext.setVisible(serverMessages.size() > 1); } } @@ -1283,7 +1284,9 @@ public class TablesPanel extends javax.swing.JPanel { if (currentMessage >= messages.size()) { currentMessage = 0; } - this.jLabelFooterText.setText(messages.get(currentMessage)); + + URLHandler.RemoveMouseAdapter(jLabelFooterText); + URLHandler.handleMessage(messages.get(currentMessage), this.jLabelFooterText); } } }//GEN-LAST:event_jButtonFooterNextActionPerformed diff --git a/Mage.Client/src/main/java/mage/client/util/URLHandler.java b/Mage.Client/src/main/java/mage/client/util/URLHandler.java new file mode 100644 index 00000000000..20d05058f60 --- /dev/null +++ b/Mage.Client/src/main/java/mage/client/util/URLHandler.java @@ -0,0 +1,119 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package mage.client.util; + +import java.awt.Desktop; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.IOException; +import java.net.URL; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import javax.swing.JLabel; + +/** + * + * @author Dahny + */ +public class URLHandler { + + private static MouseAdapter currentMouseAdapter; + + /** + * This method makes a URL in a message click-able and converts the message + * into HTML. + * + * @param message: The selected message + * @param label: The message of the day label + */ + public static void handleMessage(String message, JLabel label) { + String url = detectURL(message); + + if (!url.equals("")) { + label.addMouseListener(createMouseAdapter(url)); + } + + label.setText(convertToHTML(message)); + } + + public static void RemoveMouseAdapter(JLabel label) { + label.removeMouseListener(currentMouseAdapter); + currentMouseAdapter = null; + } + + private static MouseAdapter createMouseAdapter(String url) { + currentMouseAdapter = new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() > 0) { + if (Desktop.isDesktopSupported()) { + Desktop desktop = Desktop.getDesktop(); + try { + URI uri = new URI(url); + desktop.browse(uri); + } catch (IOException | URISyntaxException ex) { + // do nothing + } + } else { + //do nothing + } + } + } + }; + + return currentMouseAdapter; + } + + public static String convertToHTML(String input) { + String s = input; + String output = ""; + // separate the input by spaces + String[] parts = s.split("\\s+"); + + for (String item : parts) { + try { + URL url = new URL(item); + // The item is a valid URL + output = output + "" + url + " "; + + } catch (MalformedURLException e) { + //The item might still be an URL + if (item.startsWith("www.")) { + output = output + "" + item + " "; + } else { + output = output + item + " "; + } + + } + } + + output = output + ""; + return output; + } + + public static String detectURL(String input) { + String s = input; + String output = ""; + // separate the input by spaces + String[] parts = s.split("\\s+"); + + for (String item : parts) { + try { + URL url = new URL(item); + // The item is a valid URL + output = url.toString(); + } catch (MalformedURLException e) { + //The item might still be an URL + if (item.startsWith("www.")) { + output = "http://" + item; + } + } + } + + return output; + } + +} From aa96caa554e3ce9517fe3594510e13b157519c26 Mon Sep 17 00:00:00 2001 From: Danny Plenge Date: Thu, 29 Mar 2018 12:54:43 +0200 Subject: [PATCH 3/5] Removed useless else case --- .../src/main/java/mage/client/util/URLHandler.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/util/URLHandler.java b/Mage.Client/src/main/java/mage/client/util/URLHandler.java index 20d05058f60..6208bddbf71 100644 --- a/Mage.Client/src/main/java/mage/client/util/URLHandler.java +++ b/Mage.Client/src/main/java/mage/client/util/URLHandler.java @@ -57,9 +57,7 @@ public class URLHandler { } catch (IOException | URISyntaxException ex) { // do nothing } - } else { - //do nothing - } + } } } }; @@ -76,11 +74,11 @@ public class URLHandler { for (String item : parts) { try { URL url = new URL(item); - // The item is a valid URL + // The item is already a valid URL output = output + "" + url + " "; } catch (MalformedURLException e) { - //The item might still be an URL + //The item might still be a URL if (item.startsWith("www.")) { output = output + "" + item + " "; } else { @@ -103,10 +101,10 @@ public class URLHandler { for (String item : parts) { try { URL url = new URL(item); - // The item is a valid URL + // The item is already a valid URL output = url.toString(); } catch (MalformedURLException e) { - //The item might still be an URL + //The item might still be a URL if (item.startsWith("www.")) { output = "http://" + item; } From a7ffaafcb04d257f4eeff87220880df4a61fe51d Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Thu, 29 Mar 2018 13:53:59 +0200 Subject: [PATCH 4/5] made close call safe --- Mage.Client/src/main/java/org/mage/card/arcane/Util.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Mage.Client/src/main/java/org/mage/card/arcane/Util.java b/Mage.Client/src/main/java/org/mage/card/arcane/Util.java index 2645c314dbb..cbc3af717bd 100644 --- a/Mage.Client/src/main/java/org/mage/card/arcane/Util.java +++ b/Mage.Client/src/main/java/org/mage/card/arcane/Util.java @@ -1,5 +1,7 @@ package org.mage.card.arcane; +import mage.util.StreamUtils; + import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; @@ -42,7 +44,7 @@ public final class Util { socket = new DatagramSocket(); broadcast(socket, data, port, NetworkInterface.getNetworkInterfaces()); } finally { - socket.close(); + StreamUtils.closeQuietly(socket); } } From fed2f317894b3879fe18f36c4a3821f385c3d0b6 Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Thu, 29 Mar 2018 13:56:10 +0200 Subject: [PATCH 5/5] set lock calls back to original place as per request --- Mage.Server/src/main/java/mage/server/ChatSession.java | 2 +- Mage.Server/src/main/java/mage/server/UserManager.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Mage.Server/src/main/java/mage/server/ChatSession.java b/Mage.Server/src/main/java/mage/server/ChatSession.java index e73c0410f15..da9b897f7e7 100644 --- a/Mage.Server/src/main/java/mage/server/ChatSession.java +++ b/Mage.Server/src/main/java/mage/server/ChatSession.java @@ -90,8 +90,8 @@ public class ChatSession { String userName = clients.get(userId); if (reason != DisconnectReason.LostConnection) { // for lost connection the user will be reconnected or session expire so no removeUserFromAllTablesAndChat of chat yet final Lock w = lock.writeLock(); + w.lock(); try { - w.lock(); clients.remove(userId); } finally { w.unlock(); diff --git a/Mage.Server/src/main/java/mage/server/UserManager.java b/Mage.Server/src/main/java/mage/server/UserManager.java index 767f7bb59a5..cd30111745a 100644 --- a/Mage.Server/src/main/java/mage/server/UserManager.java +++ b/Mage.Server/src/main/java/mage/server/UserManager.java @@ -231,8 +231,8 @@ public enum UserManager { } logger.debug("Users to remove " + toRemove.size()); final Lock w = lock.readLock(); + w.lock(); try { - w.lock(); for (User user : toRemove) { users.remove(user.getId()); }