From 18316eb5f8220f87bb72824537024249a47c532b Mon Sep 17 00:00:00 2001 From: Jonas Konrad Date: Tue, 15 Apr 2014 11:05:20 +1000 Subject: [PATCH] Implement Favicon API --- .../java/net/md_5/bungee/api/Favicon.java | 105 ++++++++++++++++++ .../java/net/md_5/bungee/api/ServerPing.java | 33 +++++- .../main/java/net/md_5/bungee/BungeeCord.java | 7 +- .../net/md_5/bungee/conf/Configuration.java | 43 +++---- .../bungee/connection/InitialHandler.java | 5 +- 5 files changed, 158 insertions(+), 35 deletions(-) create mode 100644 api/src/main/java/net/md_5/bungee/api/Favicon.java diff --git a/api/src/main/java/net/md_5/bungee/api/Favicon.java b/api/src/main/java/net/md_5/bungee/api/Favicon.java new file mode 100644 index 00000000..3235622a --- /dev/null +++ b/api/src/main/java/net/md_5/bungee/api/Favicon.java @@ -0,0 +1,105 @@ +package net.md_5.bungee.api; + +import com.google.common.io.BaseEncoding; +import com.google.gson.TypeAdapter; +import com.google.gson.internal.bind.TypeAdapters; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import javax.imageio.ImageIO; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * Favicon shown in the server list. + */ +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public class Favicon +{ + + private static final TypeAdapter FAVICON_TYPE_ADAPTER = new TypeAdapter() + { + @Override + public void write(JsonWriter out, Favicon value) throws IOException + { + TypeAdapters.STRING.write( out, value.getEncoded() ); + } + + @Override + public Favicon read(JsonReader in) throws IOException + { + return create( TypeAdapters.STRING.read( in ) ); + } + }; + + public static TypeAdapter getFaviconTypeAdapter() + { + return FAVICON_TYPE_ADAPTER; + } + + /** + * The base64 encoded favicon, including MIME header. + */ + @Getter + private final String encoded; + + /** + * Creates a favicon from an image. + * + * @param image the image to create on + * @return the created favicon instance + * @throws IllegalArgumentException if the favicon is larger than + * {@link Short#MAX_VALUE} or not of dimensions 64x64 pixels. + */ + public static Favicon create(BufferedImage image) + { + // check size + if ( image.getWidth() != 64 || image.getHeight() != 64 ) + { + throw new IllegalArgumentException( "Server icon must be exactly 64x64 pixels" ); + } + + // dump image PNG + byte[] imageBytes; + try + { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + ImageIO.write( image, "PNG", stream ); + imageBytes = stream.toByteArray(); + } catch ( IOException e ) + { + // ByteArrayOutputStream should never throw this + throw new AssertionError( e ); + } + + // encode with header + String encoded = "data:image/png;base64," + BaseEncoding.base64().encode( imageBytes ); + + // check encoded image size + if ( encoded.length() > Short.MAX_VALUE ) + { + throw new IllegalArgumentException( "Favicon file too large for server to process" ); + } + + // create + return new Favicon( encoded ); + } + + /** + * Creates a Favicon from an encoded PNG. + * + * @deprecated Use #create(java.awt.image.BufferedImage) or one of the read + * methods instead. + */ + @Deprecated + public static Favicon create(String encodedString) + { + return new Favicon( encodedString ); + } +} diff --git a/api/src/main/java/net/md_5/bungee/api/ServerPing.java b/api/src/main/java/net/md_5/bungee/api/ServerPing.java index 240cb993..9fe4fffe 100644 --- a/api/src/main/java/net/md_5/bungee/api/ServerPing.java +++ b/api/src/main/java/net/md_5/bungee/api/ServerPing.java @@ -1,12 +1,11 @@ package net.md_5.bungee.api; +import java.util.UUID; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import net.md_5.bungee.Util; -import java.util.UUID; - /** * Represents the standard list data returned by opening a server in the * Minecraft client server list, or hitting it with a packet 0xFE. @@ -73,5 +72,33 @@ public class ServerPing } } private String description; - private String favicon; + private Favicon favicon; + + @Deprecated + public ServerPing(Protocol version, Players players, String description, String favicon) + { + this( version, players, description, Favicon.create( favicon ) ); + } + + @Deprecated + public String getFavicon() + { + return getFaviconObject() == null ? null : getFaviconObject().getEncoded(); + } + + public Favicon getFaviconObject() + { + return this.favicon; + } + + @Deprecated + public void setFavicon(String favicon) + { + setFavicon( favicon == null ? null : Favicon.create( favicon ) ); + } + + public void setFavicon(Favicon favicon) + { + this.favicon = favicon; + } } diff --git a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java index 13d84250..7f1775c5 100644 --- a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java +++ b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java @@ -1,6 +1,7 @@ package net.md_5.bungee; import com.google.gson.GsonBuilder; +import net.md_5.bungee.api.Favicon; import net.md_5.bungee.api.ServerPing; import net.md_5.bungee.module.ModuleManager; import com.google.common.io.ByteStreams; @@ -126,9 +127,11 @@ public class BungeeCord extends ProxyServer @Getter private final Logger logger; public final Gson gson = new GsonBuilder() - .registerTypeAdapter( ServerPing.PlayerInfo.class, new PlayerInfoSerializer( 5 ) ).create(); + .registerTypeAdapter( ServerPing.PlayerInfo.class, new PlayerInfoSerializer( 5 ) ) + .registerTypeAdapter( Favicon.class, Favicon.getFaviconTypeAdapter() ).create(); public final Gson gsonLegacy = new GsonBuilder() - .registerTypeAdapter( ServerPing.PlayerInfo.class, new PlayerInfoSerializer( 4 ) ).create(); + .registerTypeAdapter( ServerPing.PlayerInfo.class, new PlayerInfoSerializer( 4 ) ) + .registerTypeAdapter( Favicon.class, Favicon.getFaviconTypeAdapter() ).create(); @Getter private ConnectionThrottle connectionThrottle; private final ModuleManager moduleManager = new ModuleManager(); diff --git a/proxy/src/main/java/net/md_5/bungee/conf/Configuration.java b/proxy/src/main/java/net/md_5/bungee/conf/Configuration.java index 8a50a01a..db67a670 100644 --- a/proxy/src/main/java/net/md_5/bungee/conf/Configuration.java +++ b/proxy/src/main/java/net/md_5/bungee/conf/Configuration.java @@ -1,12 +1,7 @@ package net.md_5.bungee.conf; import com.google.common.base.Preconditions; -import com.google.common.io.BaseEncoding; -import com.google.common.io.Files; import gnu.trove.map.TMap; -import java.awt.Image; -import java.awt.image.BufferedImage; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.util.Arrays; @@ -16,6 +11,7 @@ import java.util.UUID; import java.util.logging.Level; import javax.imageio.ImageIO; import lombok.Getter; +import net.md_5.bungee.api.Favicon; import net.md_5.bungee.api.ProxyConfig; import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.config.ConfigurationAdapter; @@ -55,7 +51,7 @@ public class Configuration implements ProxyConfig private Collection disabledCommands; private int throttle = 4000; private boolean ipFoward; - public String favicon; + private Favicon favicon; public void load() { @@ -67,28 +63,8 @@ public class Configuration implements ProxyConfig { try { - BufferedImage image = ImageIO.read( fav ); - if ( image != null ) - { - if ( image.getHeight() == 64 && image.getWidth() == 64 ) - { - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - ImageIO.write( image, "png", bytes ); - favicon = "data:image/png;base64," + BaseEncoding.base64().encode( bytes.toByteArray() ); - if ( favicon.length() > Short.MAX_VALUE ) - { - ProxyServer.getInstance().getLogger().log( Level.WARNING, "Favicon file too large for server to process" ); - favicon = null; - } - } else - { - ProxyServer.getInstance().getLogger().log( Level.WARNING, "Server icon must be exactly 64x64 pixels" ); - } - } else - { - ProxyServer.getInstance().getLogger().log( Level.WARNING, "Could not load server icon for unknown reason. Please double check its format." ); - } - } catch ( IOException ex ) + favicon = Favicon.create( ImageIO.read( fav ) ); + } catch ( IOException | IllegalArgumentException ex ) { ProxyServer.getInstance().getLogger().log( Level.WARNING, "Could not load server icon", ex ); } @@ -143,4 +119,15 @@ public class Configuration implements ProxyConfig } } } + + @Deprecated + public String getFavicon() + { + return getFaviconObject().getEncoded(); + } + + public Favicon getFaviconObject() + { + return favicon; + } } diff --git a/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java b/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java index 07b46ad3..8c0ee457 100644 --- a/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java +++ b/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java @@ -18,6 +18,7 @@ import lombok.RequiredArgsConstructor; import net.md_5.bungee.*; import net.md_5.bungee.api.Callback; import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.Favicon; import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.ServerPing; import net.md_5.bungee.api.chat.BaseComponent; @@ -130,7 +131,7 @@ public class InitialHandler extends PacketHandler implements PendingConnection public void handle(LegacyPing ping) throws Exception { ServerPing legacy = new ServerPing( new ServerPing.Protocol( bungee.getGameVersion(), bungee.getProtocolVersion() ), - new ServerPing.Players( listener.getMaxPlayers(), bungee.getOnlineCount(), null ), listener.getMotd(), null ); + new ServerPing.Players( listener.getMaxPlayers(), bungee.getOnlineCount(), null ), listener.getMotd(), (Favicon) null ); legacy = bungee.getPluginManager().callEvent( new ProxyPingEvent( this, legacy ) ).getResponse(); String kickMessage = ChatColor.DARK_BLUE @@ -179,7 +180,7 @@ public class InitialHandler extends PacketHandler implements PendingConnection pingBack.done( new ServerPing( new ServerPing.Protocol( bungee.getGameVersion(), protocol ), new ServerPing.Players( listener.getMaxPlayers(), bungee.getOnlineCount(), null ), - motd, BungeeCord.getInstance().config.favicon ), + motd, BungeeCord.getInstance().config.getFaviconObject() ), null ); }