Revision: ab0407a8ba9f Author: Andrew Lindesay <apl@xxxxxxxxxxxxxx> Date: Wed Nov 12 10:52:27 2014 UTC Log: package icon report https://code.google.com/p/haiku-depot-web-app/source/detail?r=ab0407a8ba9f Added:/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/controller/PkgIconSpreadsheetReportController.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/model/PkgIconConfiguration.java
Modified:/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/MediaType.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/PkgOrchestrationService.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthorizationService.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/model/Permission.java
/haikudepotserver-webapp/src/main/resources/messages.properties /haikudepotserver-webapp/src/main/resources/messages_de.properties /haikudepotserver-webapp/src/main/webapp/js/app/controller/reports.html/haikudepotserver-webapp/src/main/webapp/js/app/controller/reportscontroller.js
======================================= --- /dev/null+++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/controller/PkgIconSpreadsheetReportController.java Wed Nov 12 10:52:27 2014 UTC
@@ -0,0 +1,149 @@ +/* + * Copyright 2014, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ + +package org.haikuos.haikudepotserver.pkg.controller; + +import au.com.bytecode.opencsv.CSVWriter; +import com.google.common.base.Optional; +import com.google.common.collect.Lists; +import org.apache.cayenne.ObjectContext; +import org.apache.cayenne.configuration.server.ServerRuntime; +import org.apache.cayenne.query.PrefetchTreeNode; +import org.haikuos.haikudepotserver.dataobjects.Pkg; +import org.haikuos.haikudepotserver.dataobjects.User; +import org.haikuos.haikudepotserver.pkg.PkgOrchestrationService; +import org.haikuos.haikudepotserver.pkg.model.PkgIconConfiguration; +import org.haikuos.haikudepotserver.security.AuthorizationService; +import org.haikuos.haikudepotserver.security.model.Permission; +import org.haikuos.haikudepotserver.support.Callback; +import org.haikuos.haikudepotserver.support.web.AbstractController; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseStatus; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; + +/** + * <p>This report produces a list of icon configuration by package.</p> + */ + +@Controller +@RequestMapping("/secured/pkg/pkgiconspreadsheetreport")+public class PkgIconSpreadsheetReportController extends AbstractController {
++ private static Logger LOGGER = LoggerFactory.getLogger(PkgIconSpreadsheetReportController.class);
+ + private static String MARKER = "*"; + + @Resource + private AuthorizationService authorizationService; + + @Resource + private ServerRuntime serverRuntime; + + @Resource + private PkgOrchestrationService pkgOrchestrationService; + + @RequestMapping(value="download.csv", method = RequestMethod.GET) + public void generate(HttpServletResponse response) throws IOException { + + ObjectContext context = serverRuntime.getContext(); + + Optional<User> user = tryObtainAuthenticatedUser(context); + + if(!authorizationService.check( + context, + user.orNull(), + null, Permission.BULK_PKGICONSPREADSHEETREPORT)) {+ LOGGER.warn("attempt to access a package icon spreadsheet report, but was unauthorized");
+ throw new AuthorizationFailure(); + } + + setHeadersForCsvDownload(response, "pkgiconspreadsheetreport"); + + final CSVWriter writer = new CSVWriter(response.getWriter(), ','); + + // obtain a list of all of the possible media configurations ++ final List<PkgIconConfiguration> pkgIconConfigurations = pkgOrchestrationService.getInUsePkgIconConfigurations(context);
+ + { + List<String> headings = Lists.newArrayList(); + + headings.add("pkg-name"); + headings.add("no-icons"); ++ for (PkgIconConfiguration pkgIconConfiguration : pkgIconConfigurations) {
+ + StringBuilder heading = new StringBuilder(); ++ heading.append(pkgIconConfiguration.getMediaType().getCode());
+ + if (null != pkgIconConfiguration.getSize()) { + heading.append("@");+ heading.append(pkgIconConfiguration.getSize().toString());
+ } + + headings.add(heading.toString()); + + } ++ writer.writeNext(headings.toArray(new String[headings.size()]));
+ } + + // stream out the packages. + + long startMs = System.currentTimeMillis(); + LOGGER.info("will produce icon spreadsheet report"); + + PrefetchTreeNode prefetchTreeNode = new PrefetchTreeNode(); + prefetchTreeNode.addPath(Pkg.PKG_ICONS_PROPERTY); ++ int count = pkgOrchestrationService.each(context, prefetchTreeNode, new Callback<Pkg>() {
+ @Override + public boolean process(Pkg pkg) { + + List<String> cells = Lists.newArrayList(); + cells.add(pkg.getName()); + + cells.add(pkg.getPkgIcons().isEmpty() ? MARKER : ""); ++ for(PkgIconConfiguration pkgIconConfiguration : pkgIconConfigurations) {
+ cells.add( + pkg.getPkgIcon( + pkgIconConfiguration.getMediaType(), + pkgIconConfiguration.getSize() + ).isPresent() + ? MARKER + : ""); + } + + writer.writeNext(cells.toArray(new String[cells.size()])); + + return true; + } + }); + + LOGGER.info( + "did produce icon report for {} packages in {}ms", + count, + System.currentTimeMillis() - startMs); + + writer.close(); + + } ++ @ResponseStatus(value= HttpStatus.UNAUTHORIZED, reason="the authenticated user is not able to run the package icon spreadsheet report")
+ public class AuthorizationFailure extends RuntimeException {} + + +} ======================================= --- /dev/null+++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/model/PkgIconConfiguration.java Wed Nov 12 10:52:27 2014 UTC
@@ -0,0 +1,65 @@ +/* + * Copyright 2014, Andrew Lindesay + * Distributed under the terms of the MIT License. + */ + +package org.haikuos.haikudepotserver.pkg.model; + +import com.google.common.base.Preconditions; +import org.haikuos.haikudepotserver.dataobjects.MediaType; + +/** + * <p>A package icon has a media type and a size. This model class simply + * combines those two together.</p> + */+public class PkgIconConfiguration implements Comparable<PkgIconConfiguration> {
+ + private MediaType mediaType; + private Integer size; + + public PkgIconConfiguration(MediaType mediaType, Integer size) {+ Preconditions.checkState(null!=mediaType, "the media type is required"); + Preconditions.checkState(null==size || size > 0, "illegal size value for icon configuration");
+ this.mediaType = mediaType; + this.size = size; + } + + public MediaType getMediaType() { + return mediaType; + } + + public Integer getSize() { + return size; + } + + @Override + public String toString() { + return String.format( + "%s @ %s", + getMediaType().getCode(), + getSize().toString()); + } + + @Override + public int compareTo(PkgIconConfiguration o) {+ int result = o.getMediaType().getCode().compareTo(getMediaType().getCode());
+ + if(0==result) { + if((null==o.getSize()) != (null==getSize())) { + if(null==getSize()) { + result = 1; + } + else { + result = -1; + } + } + else { + if(null!=getSize()) { + result = o.getSize().compareTo(getSize()); + } + } + } + + return result; + } +} =======================================--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/MediaType.java Mon Feb 24 08:20:27 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/MediaType.java Wed Nov 12 10:52:27 2014 UTC
@@ -11,7 +11,9 @@ import com.google.common.collect.Iterables; import org.apache.cayenne.ObjectContext; import org.apache.cayenne.exp.ExpressionFactory; +import org.apache.cayenne.query.Ordering; import org.apache.cayenne.query.SelectQuery; +import org.apache.cayenne.query.SortOrder; import org.haikuos.haikudepotserver.dataobjects.auto._MediaType; import java.util.List; @@ -24,6 +26,13 @@ public final static String EXTENSION_PNG = "png"; + public static List<MediaType> getAll(ObjectContext context) { + Preconditions.checkNotNull(context); + SelectQuery query = new SelectQuery(MediaType.class);+ query.addOrdering(new Ordering(CODE_PROPERTY, SortOrder.ASCENDING));
+ return (List<MediaType>) context.performQuery(query); + } + /*** <p>Files can have extensions that help to signify what sort of files they are. For example, a PNG file would * have the extension "png". This method will be able to return a media type for a given file extension.</p>
=======================================--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/PkgOrchestrationService.java Sat Oct 25 20:54:51 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/PkgOrchestrationService.java Wed Nov 12 10:52:27 2014 UTC
@@ -5,10 +5,7 @@ package org.haikuos.haikudepotserver.pkg; -import com.google.common.base.Joiner; -import com.google.common.base.Optional; -import com.google.common.base.Preconditions; -import com.google.common.base.Strings; +import com.google.common.base.*; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; @@ -19,10 +16,7 @@ import org.apache.cayenne.exp.ExpressionFactory; import org.apache.cayenne.query.*; import org.haikuos.haikudepotserver.dataobjects.*; -import org.haikuos.haikudepotserver.pkg.model.BadPkgIconException; -import org.haikuos.haikudepotserver.pkg.model.BadPkgScreenshotException; -import org.haikuos.haikudepotserver.pkg.model.PkgSearchSpecification; -import org.haikuos.haikudepotserver.pkg.model.SizeLimitReachedException; +import org.haikuos.haikudepotserver.pkg.model.*; import org.haikuos.haikudepotserver.support.Callback; import org.haikuos.haikudepotserver.support.ImageHelper; import org.haikuos.haikudepotserver.support.VersionCoordinates; @@ -38,10 +32,7 @@ import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.*; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.UUID; +import java.util.*; /** * <p>This service undertakes non-trivial operations on packages.</p> @@ -478,6 +469,79 @@ return pkgIconOptional.get(); } ++ private List<MediaType> getInUsePkgIconMediaTypes(final ObjectContext context) {
+ StringBuilder queryString = new StringBuilder(); + + queryString.append("SELECT "); + queryString.append(" DISTINCT pi.");+ queryString.append(PkgIcon.MEDIA_TYPE_PROPERTY + "." + MediaType.CODE_PROPERTY);
+ queryString.append(" FROM "); + queryString.append(PkgIcon.class.getSimpleName()); + queryString.append(" pi"); + + EJBQLQuery query = new EJBQLQuery(queryString.toString()); + + final List<String> codes = context.performQuery(query); + + return Lists.transform( + codes, + new Function<String, MediaType>() { + @Override + public MediaType apply(String input) { + return MediaType.getByCode(context, input).get(); + } + } + ); + + } ++ private List<Integer> getInUsePkgIconSizes(ObjectContext context, MediaType mediaType) {
+ StringBuilder queryString = new StringBuilder(); + + queryString.append("SELECT "); + queryString.append(" DISTINCT pi."); + queryString.append(PkgIcon.SIZE_PROPERTY); + queryString.append(" FROM "); + queryString.append(PkgIcon.class.getSimpleName()); + queryString.append(" pi WHERE pi."); + queryString.append(PkgIcon.MEDIA_TYPE_PROPERTY); + queryString.append(" = :mediaType"); + + EJBQLQuery query = new EJBQLQuery(queryString.toString()); + query.setParameter("mediaType", mediaType); + + return (List<Integer>) context.performQuery(query); + } + + /**+ * <p>The packages are configured with icons. Each icon has a media type and, + * optionally a size. This method will return all of those possible media + * type + size combinations that are actually in use at the moment. The list
+ * will be unique.</p> + */ ++ public List<PkgIconConfiguration> getInUsePkgIconConfigurations(ObjectContext objectContext) { + Preconditions.checkState(null!=objectContext,"the object context must be supplied");
+ List<PkgIconConfiguration> result = Lists.newArrayList(); ++ for(MediaType mediaType : getInUsePkgIconMediaTypes(objectContext)) { + List<Integer> sizes = getInUsePkgIconSizes(objectContext, mediaType);
+ + if(sizes.isEmpty()) { + result.add(new PkgIconConfiguration(mediaType, null)); + } + else { + for(Integer size : sizes) { + result.add(new PkgIconConfiguration(mediaType, size)); + } + } + } + + Collections.sort(result); + + return result; + } // ------------------------------ // SCREENSHOT =======================================--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthorizationService.java Tue Nov 11 00:02:22 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthorizationService.java Wed Nov 12 10:52:27 2014 UTC
@@ -214,6 +214,7 @@return null!=authenticatedUser && authenticatedUser.getIsRoot();
case BULK_PKGPROMINENCESPREADSHEETREPORT: + case BULK_PKGICONSPREADSHEETREPORT: case BULK_PKGCATEGORYCOVERAGESPREADSHEETREPORT: return null!=authenticatedUser; =======================================--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/model/Permission.java Tue Nov 11 00:02:22 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/model/Permission.java Wed Nov 12 10:52:27 2014 UTC
@@ -33,7 +33,8 @@ PKG_EDITPROMINENCE(TargetType.PKG), BULK_PKGCATEGORYCOVERAGESPREADSHEETREPORT(null), - BULK_PKGPROMINENCESPREADSHEETREPORT(null); + BULK_PKGPROMINENCESPREADSHEETREPORT(null), + BULK_PKGICONSPREADSHEETREPORT(null); private TargetType requiredTargetType; =======================================--- /haikudepotserver-webapp/src/main/resources/messages.properties Tue Nov 11 00:02:22 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/messages.properties Wed Nov 12 10:52:27 2014 UTC
@@ -388,6 +388,7 @@reporting.pkgcategorycoveragespreadsheetreport.title=Package category coverage report
reporting.pkgprominencespreadsheetreport.title=Package prominence report +reporting.pkgiconspreadsheetreport.title=Package icon report # Multipage (non-AngularJS) Interface multipage.banner.title.suffix=Simple =======================================--- /haikudepotserver-webapp/src/main/resources/messages_de.properties Tue Nov 11 00:02:22 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/messages_de.properties Wed Nov 12 10:52:27 2014 UTC
@@ -382,6 +382,7 @@reporting.pkgcategorycoveragespreadsheetreport.title=Verteilung der Pakete nach Kategorien
reporting.pkgprominencespreadsheetreport.title=Empfehlungsstufen der Pakete +reporting.pkgiconspreadsheetreport.title=Icons der Pakete # Multipage (non-AngularJS) Interface multipage.banner.title.suffix=Einfach =======================================--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/reports.html Tue Nov 11 00:02:22 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/reports.html Wed Nov 12 10:52:27 2014 UTC
@@ -12,6 +12,11 @@ <a href="" ng-click="goPkgProminenceSpreadsheetReport()"><message key="reporting.pkgprominencespreadsheetreport.title"></message>
</a> + </li> + <li show-if-permission="'BULK_PKGICONSPREADSHEETREPORT'"> + <a href="" ng-click="goPkgIconSpreadsheetReport()">+ <message key="reporting.pkgiconspreadsheetreport.title"></message>
+ </a> </li> </ul> =======================================--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/reportscontroller.js Tue Nov 11 00:02:22 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/reportscontroller.js Wed Nov 12 10:52:27 2014 UTC
@@ -19,22 +19,26 @@// the random aspect here is so that it is unlikely to cache the result.
- $scope.goPkgCategoryCoverageSpreadsheetReport = function() { + function goSecuredCsvReport(pathPart) { var iframeEl = document.getElementById("download-iframe");- iframeEl.src = '/secured/pkg/pkgcategorycoveragespreadsheetreport/download.csv?hdsbtok=' +
- userState.user().token + - '&rnd=' + - _.random(0,1000); - } - - $scope.goPkgProminenceSpreadsheetReport = function() { - var iframeEl = document.getElementById("download-iframe");- iframeEl.src = '/secured/pkg/pkgprominencespreadsheetreport/download.csv?hdsbtok=' + + iframeEl.src = '/secured/'+pathPart+'/download.csv?hdsbtok=' +
userState.user().token + '&rnd=' + _.random(0,1000); } + $scope.goPkgCategoryCoverageSpreadsheetReport = function() {+ goSecuredCsvReport('pkg/pkgcategorycoveragespreadsheetreport');
+ }; + + $scope.goPkgProminenceSpreadsheetReport = function() { + goSecuredCsvReport('pkg/pkgprominencespreadsheetreport'); + }; + + $scope.goPkgIconSpreadsheetReport = function() { + goSecuredCsvReport('pkg/pkgiconspreadsheetreport'); + }; + } ] );