Commit ca45002c authored by Drahomír Karchňák's avatar Drahomír Karchňák

Merge branch 'devel' into 'master'

Devel

See merge request netmetr!23
parents ec6ae6b5 c12f7ebd
......@@ -258,19 +258,9 @@ def getVersionNameFromGit() {
}
def branchName = getBranchNameFromGit()
return stdoutVersion.toString().trim() + '-' + branchName
}
def getVersionCodeFromGit() {
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'rev-list', 'HEAD', '--count'
standardOutput = stdout
}
return stdout.toString().trim().toInteger()
}
def getBranchNameFromGit() {
def stdout = new ByteArrayOutputStream()
exec {
......@@ -281,6 +271,16 @@ def getBranchNameFromGit() {
return branchString.contains("/") ? branchString.substring(branchString.lastIndexOf("/") + 1) : branchString
}
def getVersionCodeFromGit() {
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'rev-list', 'HEAD', '--count'
standardOutput = stdout
}
return stdout.toString().trim().toInteger()
}
task wrapper(type: Wrapper) {
gradleVersion = '2.4'
}
package at.alladin.rmbt.statisticServer.export;
import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.text.FieldPosition;
import java.text.Format;
import java.util.Locale;
import javax.imageio.ImageIO;
import at.alladin.rmbt.shared.SignificantFormat;
public abstract class AbstractImageGenerator {
private static final String TEMPLATES_PATH = "templates/";
private static final String FONT_PATH = "fonts/";
private static final String FALLBACK_FONT_NAME = "Droid Sans";
/**
* Generates a image for showing the user its speed test result
* @param lang: Language of the image, currently either 'de' or 'en'
* @param upload: Upload speed in mbps
* @param download: Download speed in mbps
* @param ping: Ping in ms
* @param isp: ISP name
* @param typ: Test type (LAN, 3G, 4G, etc.)
* @param signal: Signal strength in dbm
* @param os: Plattform used for conducting the test (Android, IOS, Applet, Browser)
* @return rendered image
*/
public abstract BufferedImage generateImage(
String lang,
double upload,
double download,
double ping,
String isp,
String typ,
String signal,
String os,
String formattedTime,
String formattedDate
) throws IOException;
protected BufferedImage loadTemplateImage(String name) throws IOException {
return ImageIO.read(super.getClass().getResourceAsStream(TEMPLATES_PATH + name));
}
protected Font loadFont(String name) {
try {
InputStream fontInputStream = super.getClass().getResourceAsStream(FONT_PATH + name);
return Font.createFont(Font.TRUETYPE_FONT, fontInputStream);
} catch (FontFormatException | IOException e) {
e.printStackTrace();
System.out.println("Couldn't load font w/ name: " + name + ". Using fallback font!");
return new Font(FALLBACK_FONT_NAME, Font.PLAIN, 1);
}
}
/**
* Formats a number to 2 significant digits
* @param number the number
* @return the formatted number
*/
protected String formatNumber(double number, String lang) {
final Locale locale = new Locale(lang);
final Format format = new SignificantFormat(2, locale);
final StringBuffer buf = format.format(number, new StringBuffer(), new FieldPosition(0));
return buf.toString();
}
protected void drawCenteredString(String s, int x, int y, int w, int h, Graphics g) {
FontMetrics fm = g.getFontMetrics();
x += (w - fm.stringWidth(s)) / 2;
y += (fm.getAscent() + (h - (fm.getAscent() + fm.getDescent())) / 2);
g.drawString(s, x, y);
}
}
package at.alladin.rmbt.statisticServer.export;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.IOException;
public class ForumBannerGenerator extends AbstractImageGenerator {
private static final int BANNER_WIDTH = 560;
private static final int BANNER_HEIGHT = 274;
private static final String UTC_FORMAT = "%s UTC";
private static final String TEMPLATE_FORMAT = "forumbanner_%s.png";
private static final String PRIMARY_FONT = "Roboto-Light.ttf";
private static final String SECONDARY_FONT = "Roboto-Medium.ttf";
private Font primaryFont = null;
private Font secondaryFont = null;
private enum UnknownValueMapping {
EN("en", "Unknown"),
CS("cs", "Neznamé"),
DE("de", "Unbekannt");
private static final String FALLBACK = "n/a";
private final String lang;
private final String unknownValue;
UnknownValueMapping(String lang, String unknownValue) {
this.lang = lang;
this.unknownValue = unknownValue;
}
protected static String getUnknownValue(String lang) {
for(UnknownValueMapping item : values()) {
if(item.lang.equals(lang)) return item.unknownValue;
}
return FALLBACK;
}
}
private enum AdditionalValuePositionMapping {
EN("en", 151, 432),
CS("cs", 130, 394),
DE("de", 128, 398);
private static final AdditionalValuePositionMapping FALLBACK = AdditionalValuePositionMapping.EN;
private final String lang;
private final int firstColumnX, secondColumnX;
AdditionalValuePositionMapping(String lang, int firstColumnX, int secondColumnX) {
this.lang = lang;
this.firstColumnX = firstColumnX;
this.secondColumnX = secondColumnX;
}
protected static int getFirstColumnX(String lang) {
for(AdditionalValuePositionMapping item : values()) {
if(item.lang.equals(lang)) return item.firstColumnX;
}
return FALLBACK.firstColumnX;
}
protected static int getSecondColumnX(String lang) {
for(AdditionalValuePositionMapping item : values()) {
if(item.lang.equals(lang)) return item.secondColumnX;
}
return FALLBACK.secondColumnX;
}
}
@Override
public BufferedImage generateImage(String lang, double upload, double download, double ping, String isp, String typ, String signal, String os, String formattedTime, String formattedDate) throws IOException {
//Initial setup of final image
BufferedImage finalImage = new BufferedImage(BANNER_WIDTH, BANNER_HEIGHT, BufferedImage.TYPE_INT_RGB);
Graphics2D finalImageGraphics = finalImage.createGraphics();
finalImageGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
prepareFonts();
renderTemplate(lang, finalImageGraphics);
String downloadValue = formatNumber(download, lang);
String uploadValue = formatNumber(upload, lang);
String pingValue = formatNumber(ping, lang);
int optimalFontSize = getOptimalFontSize(
new String[] {downloadValue, uploadValue, pingValue},
new int[] {80, 74, 66, 60, 54, 48, 42, 36, 30, 24},
167,
primaryFont,
finalImageGraphics
);
if(optimalFontSize == -1) {
//At this point something went horribly wrong. Our values wont fit in the image even on the smallest size.
//We should probably give up on life at this point.
//TODO - Error handling?
System.out.println(ForumBannerGenerator.class + " Couldn't find optimal font size!");
optimalFontSize = 1; //Why not?
}
renderMainValue(downloadValue, 93, 118, optimalFontSize, finalImageGraphics);
renderMainValue(uploadValue, 280, 118, optimalFontSize, finalImageGraphics);
renderMainValue(pingValue, 469, 118, optimalFontSize, finalImageGraphics);
String unknownText = UnknownValueMapping.getUnknownValue(lang);
renderAdditionalValue(isp, unknownText, AdditionalValuePositionMapping.getFirstColumnX(lang), 219, finalImageGraphics);
renderAdditionalValue(typ, unknownText, AdditionalValuePositionMapping.getFirstColumnX(lang), 254, finalImageGraphics);
renderAdditionalValue(signal, unknownText, AdditionalValuePositionMapping.getSecondColumnX(lang), 219, finalImageGraphics);
renderAdditionalValue(os, unknownText, AdditionalValuePositionMapping.getSecondColumnX(lang), 254, finalImageGraphics);
renderTimeDateValue(formattedDate, unknownText, 550, 16, finalImageGraphics);
renderTimeDateValue(String.format(UTC_FORMAT, formattedTime), unknownText, 550, 40, finalImageGraphics);
return finalImage;
}
private void prepareFonts() {
primaryFont = loadFont(PRIMARY_FONT);
secondaryFont = loadFont(SECONDARY_FONT);
}
private void renderTemplate(String language, Graphics2D graphics) {
try {
BufferedImage templateImage = loadTemplateImage(String.format(TEMPLATE_FORMAT, language));
graphics.drawImage(templateImage, null, 0, 0);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Gets optimal size from possibleSizes for a font and maximumSize.
* @param values - Strings that need to fit in to maximumSize when rendered
* @param possibleSizes - Possible sizes to try
* @param maximumSize - All Strings from values array need to be smaller than this
* @param font - Font to use
* @param graphics - Graphics to use
* @return Actual optimal font size or -1 on failure.
*/
private int getOptimalFontSize(String[] values, int[] possibleSizes, int maximumSize, Font font, Graphics2D graphics) {
for(int currentSize : possibleSizes) {
Font currentFont = font.deriveFont((float)(currentSize));
FontMetrics fontMetrics = graphics.getFontMetrics(currentFont);
boolean success = true;
for(String currentValue : values) {
if(fontMetrics.stringWidth(currentValue) > maximumSize) {
success = false;
break;
}
}
if(success) return currentSize;
}
//Fallback - return -1
return -1;
}
private void renderMainValue(String value, int centerX, int centerY, float fontSize, Graphics2D graphics) {
graphics.setColor(Color.WHITE);
Font font = primaryFont.deriveFont(fontSize); //Need to be float
graphics.setFont(font);
FontMetrics fontMetrics = graphics.getFontMetrics(font);
int width = fontMetrics.stringWidth(value);
int height = fontMetrics.getHeight();
graphics.drawString(value, centerX - width/2f, centerY + height/2f);
}
private void renderAdditionalValue(String value, String unknownValue, int posX, int posY, Graphics2D graphics) {
if(value == null || value.isEmpty()) value = unknownValue;
graphics.setColor(Color.WHITE);
Font font = primaryFont.deriveFont(18f);
graphics.setFont(font);
FontMetrics fontMetrics = graphics.getFontMetrics(font);
int height = fontMetrics.getHeight();
graphics.drawString(value, posX, posY + height/2f);
}
private void renderTimeDateValue(String value, String unknownValue, int posX, int posY, Graphics2D graphics) {
if(value == null || value.isEmpty()) value = unknownValue;
graphics.setColor(new Color(22, 47, 103));
Font font = secondaryFont.deriveFont(20f);
graphics.setFont(font);
FontMetrics fontMetrics = graphics.getFontMetrics(font);
int width = fontMetrics.stringWidth(value);
int height = fontMetrics.getHeight();
graphics.drawString(value, posX - width, posY + height/2f);
}
}
......@@ -17,8 +17,6 @@ package at.alladin.rmbt.statisticServer.export;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
......@@ -27,12 +25,14 @@ import java.io.OutputStream;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.text.FieldPosition;
import java.text.Format;
import java.util.Locale;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.imageio.ImageIO;
......@@ -43,7 +43,6 @@ import org.restlet.representation.Representation;
import org.restlet.representation.StringRepresentation;
import org.restlet.resource.Get;
import at.alladin.rmbt.shared.SignificantFormat;
import at.alladin.rmbt.statisticServer.ServerResource;
/**
......@@ -52,6 +51,38 @@ import at.alladin.rmbt.statisticServer.ServerResource;
*/
public class ImageExport extends ServerResource {
enum TimeDateFormatting {
EN("en", "dd-MM-yyyy", "HH:mm"),
DE("de", "dd.MM.yyyy", "HH:mm"),
CS("cs", "dd.MM.yyyy", "HH:mm");
private static final TimeDateFormatting FALLBACK = TimeDateFormatting.EN;
private final String lang;
private final String date;
private final String time;
TimeDateFormatting(String lang, String date, String time) {
this.lang = lang;
this.date = date;
this.time = time;
}
public static String getDateFormat(String lang) {
for(TimeDateFormatting formatting : values()) {
if(lang.equals(formatting.lang)) return formatting.date;
}
return FALLBACK.date;
}
public static String getTimeFormat(String lang) {
for(TimeDateFormatting formatting : values()) {
if(lang.equals(formatting.lang)) return formatting.time;
}
return FALLBACK.time;
}
}
@Get
public Representation retrieve(final String entity) {
if (!getRequest().getAttributes().containsKey("lang") || !getRequest().getAttributes().containsKey("size")) {
......@@ -80,6 +111,7 @@ public class ImageExport extends ServerResource {
final String sql = "SELECT"
//+ " ('P' || t.open_uuid) open_uuid,"
//+ " to_char(t.time AT TIME ZONE 'UTC', 'YYYY-MM-DD HH24:MI') \"time\","
+ " t.time \"time\","
+ " nt.name network_type,"
//+ " t.geo_provider loc_src,"
//+ " t.zip_code,"
......@@ -88,9 +120,10 @@ public class ImageExport extends ServerResource {
+ " (t.ping_median::float / 1000000) ping_ms,"
+ " t.signal_strength,"
+ " COALESCE(prov.shortname, mprov.shortname, prov.name, mprov.name, network_operator_name, network_sim_operator) provider_name,"
+ " COALESCE(t.plattform, t.client_name) as platform "
+ " COALESCE(t.plattform, t.client_name) as platform, "
//+ " network_operator network_mcc_mnc,"
//+ " network_operator_name network_name,"
+ " network_operator_name network_name,"
+ " public_ip_as_name public_name"
//+ " network_sim_operator sim_mcc_mnc,"
//+ " nat_type \"connection\","
//+ " public_ip_asn asn,"
......@@ -123,16 +156,22 @@ public class ImageExport extends ServerResource {
final double download = ((double) rs.getInt("download_kbit")) / 1000;
final double upload = ((double) rs.getInt("upload_kbit")) / 1000;
final double ping = rs.getFloat("ping_ms");
final String isp = rs.getString("provider_name");
final String typ = rs.getString("network_type");
final String signal = rs.getString("signal_strength");
final String os = rs.getString("platform");
final Timestamp time = rs.getTimestamp("time");
final String providerName = rs.getString("provider_name");
final String networkName = rs.getString("network_name");
final String publicName = rs.getString("public_name");
final String isp = getProviderName(providerName, networkName, publicName);
final OutputRepresentation result = new OutputRepresentation(MediaType.IMAGE_PNG) {
@Override
public void write(OutputStream out) throws IOException {
ShareImageGenerator generator;
AbstractImageGenerator generator;
if (size.equals("thumbnail")) {
generator = new FacebookThumbnailGenerator();
}
......@@ -141,10 +180,24 @@ public class ImageExport extends ServerResource {
} else {
generator = new ForumBannerGenerator();
}
BufferedImage img = generator.generateImage(lang, upload, download, ping, isp, typ, signal, os);
Date date = new Date(time.getTime());
SimpleDateFormat dateFormat = new SimpleDateFormat(TimeDateFormatting.getDateFormat(lang));
SimpleDateFormat timeFormat = new SimpleDateFormat(TimeDateFormatting.getTimeFormat(lang));
BufferedImage img = generator.generateImage(
lang,
upload,
download,
ping,
isp,
typ,
signal,
os,
timeFormat.format(time),
dateFormat.format(date)
);
ImageIO.write(img, "png", out);
}
......@@ -159,122 +212,31 @@ public class ImageExport extends ServerResource {
return new StringRepresentation("invalid uuid");
}
}
public abstract class ShareImageGenerator {
/**
* Generates a image for showing the user its speed test result
* @param lang: Language of the image, currently either 'de' or 'en'
* @param upload: Upload speed in mbps
* @param download: Download speed in mbps
* @param ping: Ping in ms
* @param isp: ISP name
* @param typ: Test type (LAN, 3G, 4G, etc.)
* @param signal: Signal strength in dbm
* @param os: Plattform used for conducting the test (Android, IOS, Applet, Browser)
* @return the image OR null, if parameters are incorrect
*/
public abstract BufferedImage generateImage(String lang, double upload, double download, double ping, String isp, String typ, String signal, String os) throws IOException;
/**
* Formats a number to 2 significant digits
* @param number the number
* @return the formatted number
*/
protected String formatNumber(double number, String lang) {
final Locale locale = new Locale(lang);
final Format format = new SignificantFormat(2, locale);
final StringBuffer buf = format.format(number, new StringBuffer(), new FieldPosition(0));
return buf.toString();
}
protected void drawCenteredString(String s, int x, int y, int w, int h, Graphics g) {
FontMetrics fm = g.getFontMetrics();
x += (w - fm.stringWidth(s)) / 2;
y += (fm.getAscent() + (h - (fm.getAscent() + fm.getDescent())) / 2);
g.drawString(s, x, y);
}
}
public class ForumBannerGenerator extends ShareImageGenerator {
private String getProviderName(String providerName, String networkName, String publicName) {
if(providerName != null) return providerName;
if(networkName != null) return networkName;
@Override
public BufferedImage generateImage(String lang, double upload, double download, double ping, String isp, String typ, String signal, String os) throws IOException{
String unknownString = "";
if (lang.equals("cs")) {
unknownString = "neznámé";
} else if (lang.equals("de")) {
unknownString = "unbekannt";
} else {
unknownString = "unknown";
}
BufferedImage img = new BufferedImage(600, 200, BufferedImage.TYPE_INT_ARGB);
img.createGraphics();
Graphics2D g = (Graphics2D)img.getGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
BufferedImage img2 = ImageIO.read(getClass().getResourceAsStream("forumbanner_" + lang + ".png"));
g.drawImage(img2, null, 0, 0);
if(publicName != null){
//publicName example: TMOBILE-CZ T-Mobile Czech Republic a.s.,CZ
Pattern pattern = Pattern.compile("\\S+\\s(.+),");
Matcher matcher = pattern.matcher(publicName);
//Speeds
g.setColor(Color.black);
g.setFont(new Font("Droid Sans", Font.BOLD, 60));
g.drawString(formatNumber(download, lang), 30, 105);
g.drawString(formatNumber(upload, lang), 230, 105);
g.drawString(formatNumber(ping, lang), 440, 105);
//ISP and other information
g.setColor(Color.WHITE);
g.setFont(new Font("Verdana", Font.BOLD,16));
//cs
if (lang.equals("cs")) {
//left
g.drawString((typ == null)?unknownString:typ, 110, 168);
g.drawString((isp == null)?unknownString:isp, 110, 191);
// right
g.drawString((signal==null)?unknownString:signal + " dBm", 400, 168);
g.drawString((os==null)?unknownString:os, 400, 191);
}
else if (lang.equals("de")) { //de
//left
g.drawString((typ == null)?unknownString:typ, 110, 168);
g.drawString((isp == null)?unknownString:isp, 110, 191);
//right
g.drawString((signal==null)?unknownString:signal + " dBm", 410, 168);
g.drawString((os==