Skip to content

Commit

Permalink
Merge branch 'performance'
Browse files Browse the repository at this point in the history
  • Loading branch information
jcsirot committed Feb 23, 2015
2 parents 52d2555 + 681a9aa commit e845cae
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 83 deletions.
50 changes: 5 additions & 45 deletions src/main/java/jenkins/plugins/jobicon/CustomIconAction.java
@@ -1,5 +1,5 @@
/*
* Copyright 2011 Jean-Christophe Sirot <sirot@chelonix.com>
* Copyright 2011-2013 Jean-Christophe Sirot <sirot@chelonix.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,18 +15,11 @@
*/
package jenkins.plugins.jobicon;

import hudson.FilePath;
import hudson.model.Action;
import hudson.model.Job;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import jenkins.model.Jenkins;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;

Expand All @@ -35,15 +28,13 @@
* icon image.
*
* This action accepts the query parameter {@code size} with these
* acceptable values {@code 16x16}, {@code 24x24} and {@code 32x32}. If this
* query parameter is present the icon image is resized before being served.
* acceptable values {@code 16x16}, {@code 24x24} and {@code 32x32}.
*
* @author Jean-Christophe Sirot
*/
public class CustomIconAction implements Action
{
private final Job job;
private final Map<Integer, byte[]> cache;

/**
* Creates a new {@code CustomIconAction}.
Expand All @@ -53,7 +44,6 @@ public class CustomIconAction implements Action
public CustomIconAction(Job job)
{
this.job = job;
this.cache = new HashMap<Integer, byte[]>();
}

@Override
Expand Down Expand Up @@ -84,39 +74,9 @@ public void doDynamic(StaplerRequest req, StaplerResponse rsp)
throws IOException, ServletException, InterruptedException
{
CustomIconProperty prop = (CustomIconProperty) job.getProperty(CustomIconProperty.class);
FilePath fpath = Jenkins.getInstance().getRootPath()
.child("userContent").child(CustomIconProperty.PATH)
.child(prop.iconfile);
URL url = fpath.toURI().toURL();
InputStream in;
String iconFilename = prop.iconfile;
String size = req.getParameter("size");
if ("16x16".equals(size)) {
in = new ByteArrayInputStream(resize(url, 16));
} else if ("24x24".equals(size)) {
in = new ByteArrayInputStream(resize(url, 24));
} else if ("32x32".equals(size)) {
in = new ByteArrayInputStream(resize(url, 32));
} else {
in = url.openStream();
}
rsp.serveFile(req, in, 0, 0, -1, fpath.getName());
rsp.sendRedirect(ImageUtils.getIconURL(iconFilename, size));
}

/**
* Resizes the icon image or gets it from cache.
*
* @param url the original image URL
* @param size the requested size
* @return the resized image data
*/
private byte[] resize(URL url, int size) throws IOException
{
if (cache.get(size) != null) {
return cache.get(size);
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
ImageUtils.resize(url.openStream(), out, size);
cache.put(size, out.toByteArray());
return out.toByteArray();
}
}
41 changes: 41 additions & 0 deletions src/main/java/jenkins/plugins/jobicon/CustomIconPlugin.java
@@ -0,0 +1,41 @@
/*
* Copyright 2013 Jean-Christophe Sirot <sirot@chelonix.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jenkins.plugins.jobicon;

import hudson.FilePath;
import hudson.Plugin;
import jenkins.model.Jenkins;

/**
* Handle migration of data between versions
*
* @author Jean-Christophe Sirot
*/
public class CustomIconPlugin extends Plugin
{
@Override
public void start() throws Exception
{
super.start();
FilePath path = Jenkins.getInstance().getRootPath().child("userContent").child(ImageUtils.PATH);
if (path.exists()) {
FilePath[] icons = path.list("*.png");
for(FilePath icon: icons) {
ImageUtils.moveIcon(icon);
}
}
}
}
37 changes: 6 additions & 31 deletions src/main/java/jenkins/plugins/jobicon/CustomIconProperty.java
@@ -1,5 +1,5 @@
/*
* Copyright 2011 Jean-Christophe Sirot <sirot@chelonix.com>
* Copyright 2011-2013 Jean-Christophe Sirot <sirot@chelonix.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,8 +15,6 @@
*/
package jenkins.plugins.jobicon;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
Expand Down Expand Up @@ -49,8 +47,6 @@
*/
public class CustomIconProperty extends JobProperty<Job<?, ?>>
{
public static final String PATH = "customIcon";

public final String iconfile;

@DataBoundConstructor
Expand Down Expand Up @@ -126,7 +122,7 @@ public void doGlobalIconsTable(StaplerRequest req, StaplerResponse rsp)
public void doDeleteIcon(StaplerRequest req, StaplerResponse rsp)
throws IOException, ServletException, InterruptedException
{
deleteIcon(req.getParameter("icon"));
ImageUtils.deleteIcon(req.getParameter("icon"));
doGlobalIconsTable(req, rsp);
}

Expand All @@ -152,16 +148,11 @@ public void doUpload(StaplerRequest req, StaplerResponse rsp,
//filename = file.getName().replaceFirst(".*/", "").replaceAll("[^\\w.,;:()#@!=+-]", "_");
MessageDigest dg = MessageDigest.getInstance("SHA1");
String filename = Hex.encodeHexString(dg.digest(file.get())) + ".png";
FilePath iconDir = jenkins.getRootPath().child("userContent").child(PATH);
iconDir.mkdirs();
FilePath icon = iconDir.child(filename);
if (icon.exists()) {
if (ImageUtils.exists(filename)) {
error = Messages.Upload_dup();
} else {
ImageUtils.storeIcon(filename, file.get());
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
ImageUtils.resize(file.getInputStream(), out, 64);
icon.copyFrom(new ByteArrayInputStream(out.toByteArray()));
icon.chmod(0644);
}
rsp.setContentType("text/html");
rsp.getWriter().println(
Expand All @@ -178,7 +169,7 @@ public void doUpload(StaplerRequest req, StaplerResponse rsp,
public List<String> getIcons() throws IOException, InterruptedException
{
FilePath iconDir = Jenkins.getInstance().getRootPath()
.child("userContent").child(PATH);
.child("userContent").child(ImageUtils.PATH).child(ImageUtils.Size.ORIGIN.directory);
if (!iconDir.exists()) {
return Collections.EMPTY_LIST;
}
Expand All @@ -191,22 +182,6 @@ public List<String> getIcons() throws IOException, InterruptedException
return names;
}

/**
* Delete an icon.
* @param id the icon id
* @throws IOException
* @throws InterruptedException
*/
private void deleteIcon(String id) throws IOException, InterruptedException
{
FilePath iconFile = Jenkins.getInstance().getRootPath()
.child("userContent").child(PATH)
.child(id + ".png");
if (iconFile.exists()) {
iconFile.delete();
}
}

/**
* Indicates if any icon has been loaded.
*
Expand Down
118 changes: 116 additions & 2 deletions src/main/java/jenkins/plugins/jobicon/ImageUtils.java
Expand Up @@ -17,18 +17,25 @@

import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.imageio.ImageIO;

import hudson.FilePath;
import jenkins.model.Jenkins;

/**
* Utility functions for images.
* Utility functions for image manipulations.
*
* @author Jean-Christophe Sirot
*/
class ImageUtils
{
public static final String PATH = "customIcon";

private ImageUtils()
{
}
Expand All @@ -49,4 +56,111 @@ static void resize(InputStream in, OutputStream out, int size) throws IOExceptio
g.dispose();
ImageIO.write(resizedImage, "png", out);
}
}

/**
* Supported icon sizes and the directory where icons are stored.
*/
enum Size {
ORIGIN("origin", 0),
SIZE_16("16x16", 16),
SIZE_24("24x24", 24),
SIZE_32("32x32", 32);

public String directory;

public int size;
Size(String dir, int size)
{
this.directory = dir;
this.size = size;
}

/**
* Tests whether the given size is valid. The size is given using the SSxSS notation.
* @param size the icon size
* @return {@code true} if the size is valid, {@code false} otherwise
*/
static boolean isValid(String size) {
return "16x16".equals(size) | "24x24".equals(size) | "32x32".equals(size);
}
}

/**
* Tests if an icon has already been uploaded
* @param filename the icon filename with the extension
* @return {@code true} if the icon exists
* @throws IOException
* @throws InterruptedException
*/
public static boolean exists(String filename) throws IOException, InterruptedException
{
return Jenkins.getInstance().getRootPath().child("userContent")
.child(PATH).child(Size.ORIGIN.directory).child(filename).exists();
}

private static void storeIcon(FilePath dir, ImageUtils.Size size, String name, byte[] data)
throws IOException, InterruptedException
{
FilePath iconDir = dir.child(size.directory);
iconDir.mkdirs();
ByteArrayInputStream in = new ByteArrayInputStream(data);
FilePath icon = iconDir.child(name);
if (size != ImageUtils.Size.ORIGIN) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ImageUtils.resize(in, out, size.size);
in = new ByteArrayInputStream(out.toByteArray());
}
icon.copyFrom(in);
icon.chmod(0644);
}

static void storeIcon(String name, byte[] data) throws IOException, InterruptedException
{
FilePath iconDir = Jenkins.getInstance().getRootPath().child("userContent").child(PATH);
storeIcon(iconDir, ImageUtils.Size.ORIGIN, name, data);
storeIcon(iconDir, ImageUtils.Size.SIZE_16, name, data);
storeIcon(iconDir, ImageUtils.Size.SIZE_24, name, data);
storeIcon(iconDir, ImageUtils.Size.SIZE_32, name, data);
}

private static void deleteIcon(FilePath dir, ImageUtils.Size size, String name)
throws IOException, InterruptedException
{
FilePath iconFile = dir.child(size.directory).child(name);
if (iconFile.exists()) {
iconFile.delete();
}
}

/**
* Delete an icon.
* @param id the icon id
* @throws IOException
* @throws InterruptedException
*/
static void deleteIcon(String id) throws IOException, InterruptedException
{
FilePath iconDir = Jenkins.getInstance().getRootPath().child("userContent").child(PATH);
deleteIcon(iconDir, ImageUtils.Size.ORIGIN, id + ".png");
deleteIcon(iconDir, ImageUtils.Size.SIZE_16, id + ".png");
deleteIcon(iconDir, ImageUtils.Size.SIZE_24, id + ".png");
deleteIcon(iconDir, ImageUtils.Size.SIZE_32, id + ".png");
}

public static void moveIcon(FilePath icon) throws IOException, InterruptedException
{
ByteArrayOutputStream out = new ByteArrayOutputStream();
icon.copyTo(out);
storeIcon(icon.getName(), out.toByteArray());
icon.delete();
}

static String getIconURL(String iconFilename, String size)
{
if (! Size.isValid(size)) {
size = Size.ORIGIN.directory;
}
return String.format("%s%s/%s/%s/%s", Jenkins.getInstance().getRootUrl(), "userContent",
PATH, size, iconFilename);
}
}
Expand Up @@ -18,7 +18,7 @@
<td>
<j:if test="${job.getProperty('jenkins.plugins.jobicon.CustomIconProperty')!=null}">
<a href="${job.shortUrl}" title="${job.name}">
<img src="${job.shortUrl}customIcon/?size=${subIconSize}" class="icon${iconSize}" />
<img src="${job.shortUrl}customIcon/?size=${iconSize}" class="icon${iconSize}" />
</a>
</j:if>
</td>
Expand Down
Expand Up @@ -36,8 +36,8 @@
<td data="${it.getBuildColumnSortData(build)}">
<a href="${h.getRelativeLinkTo(build.parent)}/${build.number}" tooltip="${build.description}">
<img src="${imagesURL}/${iconSize}/${build.buildStatusUrl}"
alt="${build.iconColor.description}"
title="${build.iconColor.description}" class="icon${iconSize}"/>
alt="${build.iconColor.description}"
title="${build.iconColor.description}" class="icon${iconSize}"/>
${build.displayName}
</a>
</td>
Expand Down
Expand Up @@ -32,7 +32,7 @@
<j:forEach var="i" items="${row}">
<j:set var="path" value="${i}.png" />
<td>
<img src="${rootURL}/userContent/customIcon/${path}" height="32" width="32"/>
<img src="${rootURL}/userContent/customIcon/32x32/${path}" height="32" width="32"/>
<f:radio name="iconfile" checked="${instance.iconfile == path}" title="" value="${path}"/>
</td>
</j:forEach>
Expand Down

0 comments on commit e845cae

Please sign in to comment.