[haiku-depot-web] [haiku-depot-web-app] 6 new revisions pushed by haiku.li...@xxxxxxxxx on 2015-01-03 09:31 GMT

  • From: haiku-depot-web-app@xxxxxxxxxxxxxx
  • To: haiku-depot-web@xxxxxxxxxxxxx
  • Date: Sat, 03 Jan 2015 09:32:47 +0000

master moved from f7eb9e811f2c to c9115c719b9d

6 new revisions:

Revision: 71a042068116
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Wed Dec 31 10:32:57 2014 UTC
Log: split the pkg/category "report" into "export" / "import" functions...
https://code.google.com/p/haiku-depot-web-app/source/detail?r=71a042068116

Revision: 8f577ae37799
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Fri Jan  2 10:56:12 2015 UTC
Log: introduce a user interface for the package category coverage import pr...
https://code.google.com/p/haiku-depot-web-app/source/detail?r=8f577ae37799

Revision: ddd7d05babcd
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Sat Jan  3 09:08:22 2015 UTC
Log:      german translations for pkg category import process
https://code.google.com/p/haiku-depot-web-app/source/detail?r=ddd7d05babcd

Revision: a02e6acf3bf4
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Sat Jan  3 09:24:53 2015 UTC
Log:      localization correction
https://code.google.com/p/haiku-depot-web-app/source/detail?r=a02e6acf3bf4

Revision: 9aec7bcde339
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Sat Jan  3 09:29:51 2015 UTC
Log:      version 1.0.14
https://code.google.com/p/haiku-depot-web-app/source/detail?r=9aec7bcde339

Revision: c9115c719b9d
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Sat Jan  3 09:29:51 2015 UTC
Log:      version 1.0.15-SNAPSHOT
https://code.google.com/p/haiku-depot-web-app/source/detail?r=c9115c719b9d

==============================================================================
Revision: 71a042068116
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Wed Dec 31 10:32:57 2014 UTC
Log:      split the pkg/category "report" into "export" / "import" functions
introduce integration tests for the "export" / "import" functions
introduce functionality to show allow a default architecture to be selected

https://code.google.com/p/haiku-depot-web-app/source/detail?r=71a042068116

Added:
/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/QueuePkgCategoryCoverageExportSpreadsheetJobRequest.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/QueuePkgCategoryCoverageExportSpreadsheetJobResult.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/job/model/JobRunnerException.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/AbstractPkgCategorySpreadsheetJobRunner.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/PkgCategoryCoverageExportSpreadsheetJobRunner.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/PkgCategoryCoverageImportSpreadsheetJobRunner.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/model/PkgCategoryCoverageExportSpreadsheetJobSpecification.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/model/PkgCategoryCoverageImportSpreadsheetJobSpecification.java /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotserver/pkg/PkgCategoryCoverageExportSpreadsheetJobRunnerIT.java /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotserver/pkg/PkgCategoryCoverageImportSpreadsheetJobRunnerIT.java /haikudepotserver-webapp/src/test/resources/sample-pkgcategorycoverageexportspreadsheet-generated.csv /haikudepotserver-webapp/src/test/resources/sample-pkgcategorycoverageimportspreadsheet-generated.csv /haikudepotserver-webapp/src/test/resources/sample-pkgcategorycoverageimportspreadsheet-supplied.csv
Deleted:
/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/QueuePkgCategoryCoverageSpreadsheetJobRequest.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/QueuePkgCategoryCoverageSpreadsheetJobResult.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/PkgCategoryCoverageSpreadsheetJobRunner.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/model/PkgCategoryCoverageSpreadsheetJobSpecification.java
Modified:
/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/PkgApi.java
 /haikudepotserver-rpm-common/src/main/resources/config__config.properties
/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/PkgApiImpl.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/PkgVersionLocalization.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/job/AbstractJobRunner.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/job/JobOrchestrationService.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/job/JobRunner.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/job/LocalJobOrchestrationServiceImpl.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/job/controller/JobController.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/constants.js
/haikudepotserver-webapp/src/main/webapp/js/app/controller/homecontroller.js
 /haikudepotserver-webapp/src/main/webapp/js/app/controller/reports.html
/haikudepotserver-webapp/src/main/webapp/js/app/controller/reportscontroller.js /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotserver/AbstractIntegrationTest.java /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotserver/job/TestJobOrchestrationServiceImpl.java

=======================================
--- /dev/null
+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/QueuePkgCategoryCoverageExportSpreadsheetJobRequest.java Wed Dec 31 10:32:57 2014 UTC
@@ -0,0 +1,9 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.pkg;
+
+public class QueuePkgCategoryCoverageExportSpreadsheetJobRequest {
+}
=======================================
--- /dev/null
+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/QueuePkgCategoryCoverageExportSpreadsheetJobResult.java Wed Dec 31 10:32:57 2014 UTC
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.pkg;
+
+public class QueuePkgCategoryCoverageExportSpreadsheetJobResult {
+
+    public String guid;
+
+    public QueuePkgCategoryCoverageExportSpreadsheetJobResult() {
+    }
+
+ public QueuePkgCategoryCoverageExportSpreadsheetJobResult(String guid) {
+        this.guid = guid;
+    }
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/job/model/JobRunnerException.java Wed Dec 31 10:32:57 2014 UTC
@@ -0,0 +1,9 @@
+package org.haikuos.haikudepotserver.job.model;
+
+public class JobRunnerException extends Exception {
+
+    public JobRunnerException(String message) {
+        super(message);
+    }
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/AbstractPkgCategorySpreadsheetJobRunner.java Wed Dec 31 10:32:57 2014 UTC
@@ -0,0 +1,52 @@
+package org.haikuos.haikudepotserver.pkg;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Lists;
+import org.apache.cayenne.configuration.server.ServerRuntime;
+import org.haikuos.haikudepotserver.dataobjects.PkgCategory;
+import org.haikuos.haikudepotserver.job.AbstractJobRunner;
+import org.haikuos.haikudepotserver.job.model.JobSpecification;
+
+import javax.annotation.Resource;
+import java.util.Collections;
+import java.util.List;
+
+public abstract class AbstractPkgCategorySpreadsheetJobRunner<T extends JobSpecification>
+        extends AbstractJobRunner<T> {
+
+    /**
+ * <p>This string is inserted into a cell in order to indicate that the combination of the
+     * package on the row and the category on the column are true.</p>
+     */
+
+    protected static String MARKER = "*";
+
+    @Resource
+    protected ServerRuntime serverRuntime;
+
+    @Resource
+    protected PkgOrchestrationService pkgOrchestrationService;
+
+    protected List<String> getPkgCategoryCodes() {
+        return Lists.transform(
+                PkgCategory.getAll(serverRuntime.getContext()),
+                new Function<PkgCategory, String>() {
+                    @Override
+                    public String apply(PkgCategory input) {
+                        return input.getCode();
+                    }
+                }
+        );
+    }
+
+    protected String[] getHeadingRow(List<String> pkgCategoryCodes) {
+        List<String> headings = Lists.newArrayList();
+        headings.add("pkg-name");
+        headings.add("any-summary");
+        headings.add("none");
+ Collections.addAll(headings, pkgCategoryCodes.toArray(new String[pkgCategoryCodes.size()]));
+        headings.add("action");
+        return headings.toArray(new String[headings.size()]);
+    }
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/PkgCategoryCoverageExportSpreadsheetJobRunner.java Wed Dec 31 10:32:57 2014 UTC
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.pkg;
+
+import au.com.bytecode.opencsv.CSVWriter;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import com.google.common.net.MediaType;
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.query.PrefetchTreeNode;
+import org.haikuos.haikudepotserver.dataobjects.Architecture;
+import org.haikuos.haikudepotserver.dataobjects.Pkg;
+import org.haikuos.haikudepotserver.dataobjects.PkgVersionLocalization;
+import org.haikuos.haikudepotserver.job.JobOrchestrationService;
+import org.haikuos.haikudepotserver.job.model.JobDataWithByteSink;
+import org.haikuos.haikudepotserver.job.model.JobRunnerException;
+import org.haikuos.haikudepotserver.pkg.model.PkgCategoryCoverageExportSpreadsheetJobSpecification;
+import org.haikuos.haikudepotserver.support.Callback;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * <p>This report is a spreadsheet that covers basic details of each package.</p>
+ */
+
+@Component
+public class PkgCategoryCoverageExportSpreadsheetJobRunner extends AbstractPkgCategorySpreadsheetJobRunner<PkgCategoryCoverageExportSpreadsheetJobSpecification> {
+
+ private static Logger LOGGER = LoggerFactory.getLogger(PkgCategoryCoverageExportSpreadsheetJobRunner.class);
+
+    @Override
+    public void run(
+            JobOrchestrationService jobOrchestrationService,
+ PkgCategoryCoverageExportSpreadsheetJobSpecification specification)
+            throws IOException, JobRunnerException {
+
+        Preconditions.checkArgument(null!=jobOrchestrationService);
+        assert null!=jobOrchestrationService;
+        Preconditions.checkArgument(null!=specification);
+
+        final ObjectContext context = serverRuntime.getContext();
+
+        // this will register the outbound data against the job.
+ JobDataWithByteSink jobDataWithByteSink = jobOrchestrationService.storeGeneratedData(
+                specification.getGuid(),
+                "download",
+                MediaType.CSV_UTF_8.toString());
+
+        try(
+ OutputStream outputStream = jobDataWithByteSink.getByteSink().openBufferedStream(); + OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
+                CSVWriter writer = new CSVWriter(outputStreamWriter, ',')
+        ) {
+
+            // headers
+
+            final List<String> pkgCategoryCodes = getPkgCategoryCodes();
+
+            List<String> headings = Lists.newArrayList();
+            headings.add("pkg-name");
+            headings.add("any-summary");
+            headings.add("none");
+ Collections.addAll(headings, pkgCategoryCodes.toArray(new String[pkgCategoryCodes.size()]));
+            headings.add("action");
+
+            long startMs = System.currentTimeMillis();
+
+ writer.writeNext(headings.toArray(new String[headings.size()]));
+
+            // stream out the packages.
+
+            PrefetchTreeNode treeNode = new PrefetchTreeNode();
+            treeNode.addPath(Pkg.PKG_PKG_CATEGORIES_PROPERTY);
+
+ LOGGER.info("will produce category coverage spreadsheet report");
+
+            int count = pkgOrchestrationService.each(
+                    context,
+                    treeNode,
+ Architecture.getAllExceptByCode(context, Collections.singleton(Architecture.CODE_SOURCE)),
+                    new Callback<Pkg>() {
+                        @Override
+                        public boolean process(Pkg pkg) {
+                            List<String> cols = Lists.newArrayList();
+ Optional<PkgVersionLocalization> locOptional = Optional.absent();
+
+                            if(null!=pkg) {
+ locOptional = PkgVersionLocalization.getAnyPkgVersionLocalizationForPkg(context, pkg);
+                            }
+
+                            cols.add(pkg.getName());
+ cols.add(locOptional.isPresent() ? locOptional.get().getSummary() : ""); + cols.add(pkg.getPkgPkgCategories().isEmpty() ? MARKER : "");
+
+ for (String pkgCategoryCode : pkgCategoryCodes) { + cols.add(pkg.getPkgPkgCategory(pkgCategoryCode).isPresent() ? MARKER : "");
+                            }
+
+                            cols.add(""); // no action
+ writer.writeNext(cols.toArray(new String[cols.size()]));
+                            return true; // keep going!
+                        }
+                    }
+            );
+
+            LOGGER.info(
+ "did produce category coverage spreadsheet report for {} packages in {}ms",
+                    count,
+                    System.currentTimeMillis() - startMs);
+
+        }
+
+    }
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/PkgCategoryCoverageImportSpreadsheetJobRunner.java Wed Dec 31 10:32:57 2014 UTC
@@ -0,0 +1,202 @@
+package org.haikuos.haikudepotserver.pkg;
+
+import au.com.bytecode.opencsv.CSVReader;
+import au.com.bytecode.opencsv.CSVWriter;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import com.google.common.net.MediaType;
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.access.Transaction;
+import org.haikuos.haikudepotserver.dataobjects.Pkg;
+import org.haikuos.haikudepotserver.dataobjects.PkgCategory;
+import org.haikuos.haikudepotserver.job.JobOrchestrationService;
+import org.haikuos.haikudepotserver.job.model.JobDataWithByteSink;
+import org.haikuos.haikudepotserver.job.model.JobDataWithByteSource;
+import org.haikuos.haikudepotserver.job.model.JobRunnerException;
+import org.haikuos.haikudepotserver.pkg.model.PkgCategoryCoverageImportSpreadsheetJobSpecification;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import java.io.*;
+import java.util.Arrays;
+import java.util.List;
+
+@Component
+public class PkgCategoryCoverageImportSpreadsheetJobRunner
+ extends AbstractPkgCategorySpreadsheetJobRunner<PkgCategoryCoverageImportSpreadsheetJobSpecification> {
+
+ private static Logger LOGGER = LoggerFactory.getLogger(PkgCategoryCoverageImportSpreadsheetJobRunner.class);
+
+    /**
+ * <p>For the import process, this enum describes the action that was taken for a given import line.</p>
+     */
+
+    private enum Action {
+        NOACTION,
+        INVALID,
+        UPDATED,
+        NOTFOUND
+    }
+
+    @Override
+    public void run(
+            JobOrchestrationService jobOrchestrationService,
+ PkgCategoryCoverageImportSpreadsheetJobSpecification specification)
+            throws IOException, JobRunnerException {
+
+        Preconditions.checkArgument(null != jobOrchestrationService);
+        assert null!=jobOrchestrationService;
+        Preconditions.checkArgument(null!=specification);
+ Preconditions.checkArgument(null!=specification.getInputDataGuid(), "missing imput data guid on specification");
+
+        // this will register the outbound data against the job.
+ JobDataWithByteSink jobDataWithByteSink = jobOrchestrationService.storeGeneratedData(
+                specification.getGuid(),
+                "download",
+                MediaType.CSV_UTF_8.toString());
+
+ // if there is input data then feed it in and process it to manipulate the packages'
+        // categories.
+
+ Optional<JobDataWithByteSource> jobDataWithByteSourceOptional = jobOrchestrationService.tryObtainData(specification.getInputDataGuid());
+
+        if(!jobDataWithByteSourceOptional.isPresent()) {
+ throw new IllegalStateException("the job data was not able to be found for guid; " + specification.getInputDataGuid());
+        }
+
+        try(
+ OutputStream outputStream = jobDataWithByteSink.getByteSink().openBufferedStream(); + OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
+                CSVWriter writer = new CSVWriter(outputStreamWriter, ',');
+ InputStream inputStream = jobDataWithByteSourceOptional.get().getByteSource().openStream(); + InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
+                CSVReader reader = new CSVReader(inputStreamReader);
+        ) {
+
+            // headers
+
+            List<String> pkgCategoryCodes = getPkgCategoryCodes();
+            String[] headings = getHeadingRow(pkgCategoryCodes);
+
+ // read in the first row of the input and check the headings are there to quasi-validate
+            // that the input is not some random rubbish.
+
+            String[] row = reader.readNext();
+
+            if(headings.length != row.length) {
+ throw new JobRunnerException("wrong number of header columns in input");
+            }
+
+            if(!Arrays.equals(row,headings)) {
+                throw new JobRunnerException("mismatched input headers");
+            }
+
+            writer.writeNext(headings);
+
+            // start a cayenne long-running txn
+ Transaction transaction = serverRuntime.getDataDomain().createTransaction();
+            Transaction.bindThreadTransaction(transaction);
+
+            try {
+
+                while (null != (row = reader.readNext())) {
+                    if (0 != row.length) {
+
+ ObjectContext rowContext = serverRuntime.getContext();
+
+                        Action action = Action.NOACTION;
+
+ if (row.length < headings.length - 1) { // -1 because it is possible to omit the action column.
+                            action = Action.INVALID;
+ LOGGER.warn("inconsistent number of cells on line");
+                        } else {
+                            String pkgName = row[0];
+                            // 1; display
+                            boolean isNone = MARKER.equals(row[2]);
+
+ Optional<Pkg> pkgOptional = Pkg.getByName(rowContext, pkgName); + List<String> selectedPkgCateogryCodes = Lists.newArrayList();
+
+                            if (pkgOptional.isPresent()) {
+
+ for (int i = 0; i < pkgCategoryCodes.size(); i++) {
+                                    if (MARKER.equals(row[3 + i].trim())) {
+
+                                        if(isNone) {
+                                            action = Action.INVALID;
+ LOGGER.warn("line for package {} has 'none' marked as well as an actual category", row[0]);
+                                        }
+
+ selectedPkgCateogryCodes.add(pkgCategoryCodes.get(i));
+                                    }
+                                }
+
+                                if(action == Action.NOACTION) {
+ List<PkgCategory> selectedPkgCategories = PkgCategory.getByCodes(rowContext, selectedPkgCateogryCodes);
+
+ if (selectedPkgCategories.size() != selectedPkgCateogryCodes.size()) { + throw new IllegalStateException("one or more of the package category codes was not able to be found");
+                                    }
+
+ if (pkgOrchestrationService.updatePkgCategories(
+                                            rowContext,
+                                            pkgOptional.get(),
+                                            selectedPkgCategories)) {
+                                        action = Action.UPDATED;
+                                        rowContext.commitChanges();
+ LOGGER.debug("did update for package {}", row[0]);
+                                    }
+                                }
+
+                            }
+                            else {
+                                action = Action.NOTFOUND;
+ LOGGER.debug("unable to find the package for {}", row[0]);
+                            }
+
+                        }
+
+ // copy the row back verbatim, but with the action result at the
+                        // end.
+
+                        List<String> rowOutput = Lists.newArrayList(row);
+
+                        while (rowOutput.size() < headings.length) {
+                            rowOutput.add("");
+                        }
+
+                        rowOutput.remove(rowOutput.size()-1);
+                        rowOutput.add(action.name());
+
+ writer.writeNext(rowOutput.toArray(new String[rowOutput.size()]));
+                    }
+
+                }
+
+                transaction.commit();
+            }
+            catch(Throwable th) {
+                transaction.setRollbackOnly();
+ LOGGER.error("a problem has arisen importing package categories from a spreadsheet", th);
+            }
+            finally {
+                Transaction.bindThreadTransaction(null);
+
+ if (Transaction.STATUS_MARKED_ROLLEDBACK == transaction.getStatus()) {
+                    try {
+                        transaction.rollback();
+                    }
+                    catch(Exception e) {
+                        // ignore
+                    }
+                }
+
+            }
+
+        }
+
+    }
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/model/PkgCategoryCoverageExportSpreadsheetJobSpecification.java Wed Dec 31 10:32:57 2014 UTC
@@ -0,0 +1,16 @@
+package org.haikuos.haikudepotserver.pkg.model;
+
+import org.haikuos.haikudepotserver.job.model.AbstractJobSpecification;
+import org.haikuos.haikudepotserver.job.model.JobSpecification;
+import org.springframework.util.ObjectUtils;
+
+public class PkgCategoryCoverageExportSpreadsheetJobSpecification extends AbstractJobSpecification {
+
+    @Override
+    public boolean isEquivalent(JobSpecification other) {
+        return
+ PkgCategoryCoverageImportSpreadsheetJobSpecification.class.isAssignableFrom(other.getClass()) + && ObjectUtils.nullSafeEquals(other.getOwnerUserNickname(), getOwnerUserNickname());
+    }
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/model/PkgCategoryCoverageImportSpreadsheetJobSpecification.java Wed Dec 31 10:32:57 2014 UTC
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.pkg.model;
+
+import com.google.common.base.Strings;
+import org.haikuos.haikudepotserver.job.model.AbstractJobSpecification;
+import org.haikuos.haikudepotserver.job.model.JobSpecification;
+import org.springframework.util.ObjectUtils;
+
+import java.util.Collection;
+import java.util.Collections;
+
+public class PkgCategoryCoverageImportSpreadsheetJobSpecification extends AbstractJobSpecification {
+
+    /**
+ * <p>This identifies the spreadsheet input data that can be used to work off.</p>
+     */
+
+    private String inputDataGuid;
+
+    public String getInputDataGuid() {
+        return inputDataGuid;
+    }
+
+    public void setInputDataGuid(String inputDataGuid) {
+        this.inputDataGuid = inputDataGuid;
+    }
+
+    public Collection<String> getSuppliedDataGuids() {
+        if(!Strings.isNullOrEmpty(inputDataGuid)) {
+            return Collections.singleton(inputDataGuid);
+        }
+
+        return super.getSuppliedDataGuids();
+    }
+
+    @Override
+    public boolean isEquivalent(JobSpecification other) {
+        return
+ PkgCategoryCoverageImportSpreadsheetJobSpecification.class.isAssignableFrom(other.getClass()) + && getSuppliedDataGuids().equals(other.getSuppliedDataGuids()) + && ObjectUtils.nullSafeEquals(other.getOwnerUserNickname(), getOwnerUserNickname());
+    }
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotserver/pkg/PkgCategoryCoverageExportSpreadsheetJobRunnerIT.java Wed Dec 31 10:32:57 2014 UTC
@@ -0,0 +1,66 @@
+package org.haikuos.haikudepotserver.pkg;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Optional;
+import com.google.common.collect.Iterables;
+import com.google.common.io.ByteSource;
+import junit.framework.Assert;
+import org.haikuos.haikudepotserver.AbstractIntegrationTest;
+import org.haikuos.haikudepotserver.IntegrationTestSupportService;
+import org.haikuos.haikudepotserver.job.JobOrchestrationService;
+import org.haikuos.haikudepotserver.job.model.JobDataWithByteSource;
+import org.haikuos.haikudepotserver.job.model.JobSnapshot;
+import org.haikuos.haikudepotserver.pkg.model.PkgCategoryCoverageExportSpreadsheetJobSpecification;
+import org.junit.Test;
+import org.springframework.test.context.ContextConfiguration;
+
+import javax.annotation.Resource;
+import java.io.BufferedReader;
+import java.io.IOException;
+
+@ContextConfiguration({
+        "classpath:/spring/servlet-context.xml",
+        "classpath:/spring/test-context.xml"
+})
+public class PkgCategoryCoverageExportSpreadsheetJobRunnerIT extends AbstractIntegrationTest {
+
+    @Resource
+    private IntegrationTestSupportService integrationTestSupportService;
+
+    @Resource
+    private JobOrchestrationService jobOrchestrationService;
+
+    /**
+ * <p>Uses the sample data and checks that the output from the report matches a captured, sensible-looking
+     * previous run.</p>
+     */
+
+    @Test
+    public void testRun() throws IOException {
+
+        integrationTestSupportService.createStandardTestData();
+
+        // ------------------------------------
+        Optional<String> guidOptional = jobOrchestrationService.submit(
+                new PkgCategoryCoverageExportSpreadsheetJobSpecification(),
+                JobOrchestrationService.CoalesceMode.NONE);
+        // ------------------------------------
+
+ jobOrchestrationService.awaitJobConcludedUninterruptibly(guidOptional.get(), 10000); + Optional<? extends JobSnapshot> snapshotOptional = jobOrchestrationService.tryGetJob(guidOptional.get()); + Assert.assertEquals(snapshotOptional.get().getStatus(), JobSnapshot.Status.FINISHED);
+
+ String dataGuid = Iterables.getOnlyElement(snapshotOptional.get().getGeneratedDataGuids()); + JobDataWithByteSource jobSource = jobOrchestrationService.tryObtainData(dataGuid).get(); + ByteSource expectedByteSource = getResourceByteSource("/sample-pkgcategorycoverageexportspreadsheet-generated.csv");
+
+        try(
+ BufferedReader jobReader = jobSource.getByteSource().asCharSource(Charsets.UTF_8).openBufferedStream(); + BufferedReader sampleReader = expectedByteSource.asCharSource(Charsets.UTF_8).openBufferedStream()
+        ) {
+            assertEqualsLineByLine(sampleReader, jobReader);
+        }
+
+    }
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotserver/pkg/PkgCategoryCoverageImportSpreadsheetJobRunnerIT.java Wed Dec 31 10:32:57 2014 UTC
@@ -0,0 +1,94 @@
+package org.haikuos.haikudepotserver.pkg;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.io.ByteSource;
+import com.google.common.net.MediaType;
+import junit.framework.Assert;
+import org.apache.cayenne.ObjectContext;
+import org.fest.assertions.Assertions;
+import org.haikuos.haikudepotserver.AbstractIntegrationTest;
+import org.haikuos.haikudepotserver.IntegrationTestSupportService;
+import org.haikuos.haikudepotserver.dataobjects.Pkg;
+import org.haikuos.haikudepotserver.dataobjects.PkgPkgCategory;
+import org.haikuos.haikudepotserver.job.JobOrchestrationService;
+import org.haikuos.haikudepotserver.job.model.JobDataWithByteSource;
+import org.haikuos.haikudepotserver.job.model.JobSnapshot;
+import org.haikuos.haikudepotserver.pkg.model.PkgCategoryCoverageImportSpreadsheetJobSpecification;
+import org.junit.Test;
+import org.springframework.test.context.ContextConfiguration;
+
+import javax.annotation.Resource;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.util.Set;
+
+@ContextConfiguration({
+        "classpath:/spring/servlet-context.xml",
+        "classpath:/spring/test-context.xml"
+})
+public class PkgCategoryCoverageImportSpreadsheetJobRunnerIT extends AbstractIntegrationTest {
+
+    @Resource
+    private IntegrationTestSupportService integrationTestSupportService;
+
+    @Resource
+    private JobOrchestrationService jobOrchestrationService;
+
+    @Test
+    public void testRun() throws IOException {
+
+        integrationTestSupportService.createStandardTestData();
+
+ PkgCategoryCoverageImportSpreadsheetJobSpecification spec = new PkgCategoryCoverageImportSpreadsheetJobSpecification();
+        spec.setInputDataGuid(jobOrchestrationService.storeSuppliedData(
+                "input",
+                MediaType.CSV_UTF_8.toString(),
+ getResourceByteSource("/sample-pkgcategorycoverageimportspreadsheet-supplied.csv")
+        ).getGuid());
+
+        // ------------------------------------
+ Optional<String> guidOptional = jobOrchestrationService.submit(spec,JobOrchestrationService.CoalesceMode.NONE);
+        // ------------------------------------
+
+ jobOrchestrationService.awaitJobConcludedUninterruptibly(guidOptional.get(), 10000); + Optional<? extends JobSnapshot> snapshotOptional = jobOrchestrationService.tryGetJob(guidOptional.get()); + Assert.assertEquals(snapshotOptional.get().getStatus(), JobSnapshot.Status.FINISHED);
+
+ String dataGuid = Iterables.getOnlyElement(snapshotOptional.get().getGeneratedDataGuids()); + JobDataWithByteSource jobSource = jobOrchestrationService.tryObtainData(dataGuid).get(); + ByteSource expectedByteSource = getResourceByteSource("/sample-pkgcategorycoverageimportspreadsheet-generated.csv");
+
+        try(
+ BufferedReader jobReader = jobSource.getByteSource().asCharSource(Charsets.UTF_8).openBufferedStream(); + BufferedReader sampleReader = expectedByteSource.asCharSource(Charsets.UTF_8).openBufferedStream()
+        ) {
+            assertEqualsLineByLine(sampleReader, jobReader);
+        }
+
+ // one of the packages was changed; check that the change is in the database successfully.
+
+        {
+            ObjectContext context = serverRuntime.getContext();
+            Optional<Pkg> pkgOptional = Pkg.getByName(context, "pkg1");
+ Set<String> pkg1PkgCategoryCodes = ImmutableSet.copyOf(Iterables.transform(
+                    pkgOptional.get().getPkgPkgCategories(),
+                    new Function<PkgPkgCategory, String>() {
+
+                        @Override
+                        public String apply(PkgPkgCategory input) {
+                            return input.getPkgCategory().getCode();
+                        }
+                    }
+            ));
+
+ Assertions.assertThat(pkg1PkgCategoryCodes).isEqualTo(ImmutableSet.of("audio","graphics"));
+
+        }
+
+    }
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/test/resources/sample-pkgcategorycoverageexportspreadsheet-generated.csv Wed Dec 31 10:32:57 2014 UTC
@@ -0,0 +1,5 @@
+"pkg-name","any-summary","none","audio","business","development","education","games","graphics","internetandnetwork","productivity","scienceandmathematics","systemandutilities","video","action"
+"pkg1","pkg1Version2SummaryEnglish_persimon","","","","","","","*","","","","","",""
+"pkg2","sample summary","*","","","","","","","","","","","",""
+"pkg3","sample summary","*","","","","","","","","","","","",""
+"pkgany","sample summary","*","","","","","","","","","","","",""
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/test/resources/sample-pkgcategorycoverageimportspreadsheet-generated.csv Wed Dec 31 10:32:57 2014 UTC
@@ -0,0 +1,6 @@
+"pkg-name","any-summary","none","audio","business","development","education","games","graphics","internetandnetwork","productivity","scienceandmathematics","systemandutilities","video","action"
+"pkg1","pkg1Version2SummaryEnglish_persimon","","*","","","","","*","","","","","","UPDATED"
+"notexisting","sample summary","","","*","*","*","","","","","","","","NOTFOUND"
+"pkg3","sample summary","*","","","","","","","","","","","","NOACTION"
+"pkgany","sample summary","*","","*","","*","","","","","","","","INVALID"
+"badline","","","","","","","","","","","","","","INVALID"
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/test/resources/sample-pkgcategorycoverageimportspreadsheet-supplied.csv Wed Dec 31 10:32:57 2014 UTC
@@ -0,0 +1,6 @@
+"pkg-name","any-summary","none","audio","business","development","education","games","graphics","internetandnetwork","productivity","scienceandmathematics","systemandutilities","video","action"
+"pkg1","pkg1Version2SummaryEnglish_persimon","","*","","","","","*","","","","","",""
+"notexisting","sample summary","","","*","*","*","","","","","","","",""
+"pkg3","sample summary","*","","","","","","","","","","","",""
+"pkgany","sample summary","*","","*","","*","","","","","","","",""
+badline
=======================================
--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/QueuePkgCategoryCoverageSpreadsheetJobRequest.java Sat Dec 20 09:47:27 2014 UTC
+++ /dev/null
@@ -1,9 +0,0 @@
-/*
- * Copyright 2014, Andrew Lindesay
- * Distributed under the terms of the MIT License.
- */
-
-package org.haikuos.haikudepotserver.api1.model.pkg;
-
-public class QueuePkgCategoryCoverageSpreadsheetJobRequest {
-}
=======================================
--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/QueuePkgCategoryCoverageSpreadsheetJobResult.java Sat Dec 20 09:47:27 2014 UTC
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright 2014, Andrew Lindesay
- * Distributed under the terms of the MIT License.
- */
-
-package org.haikuos.haikudepotserver.api1.model.pkg;
-
-public class QueuePkgCategoryCoverageSpreadsheetJobResult {
-
-    public String guid;
-
-    public QueuePkgCategoryCoverageSpreadsheetJobResult() {
-    }
-
-    public QueuePkgCategoryCoverageSpreadsheetJobResult(String guid) {
-        this.guid = guid;
-    }
-
-}
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/PkgCategoryCoverageSpreadsheetJobRunner.java Sat Dec 20 09:47:27 2014 UTC
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright 2014, Andrew Lindesay
- * Distributed under the terms of the MIT License.
- */
-
-package org.haikuos.haikudepotserver.pkg;
-
-import au.com.bytecode.opencsv.CSVWriter;
-import com.google.common.base.Function;
-import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
-import com.google.common.collect.Lists;
-import com.google.common.net.MediaType;
-import org.apache.cayenne.ObjectContext;
-import org.apache.cayenne.configuration.server.ServerRuntime;
-import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.exp.ExpressionFactory;
-import org.apache.cayenne.query.PrefetchTreeNode;
-import org.apache.cayenne.query.SelectQuery;
-import org.haikuos.haikudepotserver.dataobjects.*;
-import org.haikuos.haikudepotserver.job.AbstractJobRunner;
-import org.haikuos.haikudepotserver.job.JobOrchestrationService;
-import org.haikuos.haikudepotserver.job.model.JobDataWithByteSink;
-import org.haikuos.haikudepotserver.pkg.model.PkgCategoryCoverageSpreadsheetJobSpecification;
-import org.haikuos.haikudepotserver.support.Callback;
-import org.haikuos.haikudepotserver.support.cayenne.ExpressionHelper;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.stereotype.Component;
-
-import javax.annotation.Resource;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * <p>This report is a spreadsheet that covers basic details of each package.</p>
- */
-
-@Component
-public class PkgCategoryCoverageSpreadsheetJobRunner extends AbstractJobRunner<PkgCategoryCoverageSpreadsheetJobSpecification> {
-
- private static Logger LOGGER = LoggerFactory.getLogger(PkgCategoryCoverageSpreadsheetJobRunner.class);
-
-    /**
- * <p>This string is inserted into a cell in order to indicate that the combination of the
-     * package on the row and the category on the column are true.</p>
-     */
-
-    private static String MARKER = "*";
-
-    @Resource
-    private ServerRuntime serverRuntime;
-
-    @Resource
-    private PkgOrchestrationService pkgOrchestrationService;
-
-    /**
- * <p>This will go out and find the first package localization it can find on the latest package
-     * versions.</p>
-     */
-
- private Optional<PkgVersionLocalization> getAnyPkgVersionLocalizationForPkg(ObjectContext context, Pkg pkg) {
-
-        List<Expression> expressions = Lists.newArrayList();
-
-        expressions.add(ExpressionFactory.matchExp(
- PkgVersionLocalization.PKG_VERSION_PROPERTY + "." + PkgVersion.PKG_PROPERTY + ".",
-                pkg));
-
-        expressions.add(ExpressionFactory.matchExp(
- PkgVersionLocalization.PKG_VERSION_PROPERTY + "." + PkgVersion.IS_LATEST_PROPERTY,
-                true));
-
-        expressions.add(ExpressionFactory.matchExp(
- PkgVersionLocalization.PKG_VERSION_PROPERTY + "." + PkgVersion.ACTIVE_PROPERTY,
-                true));
-
-        SelectQuery query = new SelectQuery(
-                PkgVersionLocalization.class,
-                ExpressionHelper.andAll(expressions));
-
- List<PkgVersionLocalization> locs = (List<PkgVersionLocalization>) context.performQuery(query);
-
-        if(locs.isEmpty()) {
-            return Optional.absent();
-        }
-
-        return Optional.of(locs.get(0));
-    }
-
-    @Override
-    public void run(
-            JobOrchestrationService jobOrchestrationService,
- PkgCategoryCoverageSpreadsheetJobSpecification specification) throws IOException {
-
-        Preconditions.checkArgument(null!=jobOrchestrationService);
-        assert null!=jobOrchestrationService;
-        Preconditions.checkArgument(null!=specification);
-
-        final ObjectContext context = serverRuntime.getContext();
-
-        // this will register the outbound data against the job.
- JobDataWithByteSink jobDataWithByteSink = jobOrchestrationService.storeGeneratedData(
-                specification.getGuid(),
-                "download",
-                MediaType.CSV_UTF_8.toString());
-
-        try(
- OutputStream outputStream = jobDataWithByteSink.getByteSink().openBufferedStream(); - OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
-                CSVWriter writer = new CSVWriter(outputStreamWriter, ',')
-        ) {
-
-            // headers
-
-            final List<String> pkgCategoryCodes = Lists.transform(
-                    PkgCategory.getAll(context),
-                    new Function<PkgCategory, String>() {
-                        @Override
-                        public String apply(PkgCategory input) {
-                            return input.getCode();
-                        }
-                    }
-            );
-
-            List<String> headings = Lists.newArrayList();
-            headings.add("pkg-name");
-            headings.add("any-summary");
-            headings.add("none");
- Collections.addAll(headings, pkgCategoryCodes.toArray(new String[pkgCategoryCodes.size()]));
-
- writer.writeNext(headings.toArray(new String[headings.size()]));
-
-            // stream out the packages.
-
-            PrefetchTreeNode treeNode = new PrefetchTreeNode();
-            treeNode.addPath(Pkg.PKG_PKG_CATEGORIES_PROPERTY);
-
-            long startMs = System.currentTimeMillis();
- LOGGER.info("will produce category coverage spreadsheet report");
-
-            int count = pkgOrchestrationService.each(
-                    context,
-                    treeNode,
- Architecture.getAllExceptByCode(context, Collections.singleton(Architecture.CODE_SOURCE)),
-                    new Callback<Pkg>() {
-                        @Override
-                        public boolean process(Pkg pkg) {
- Optional<PkgVersionLocalization> locOptional = getAnyPkgVersionLocalizationForPkg(context, pkg);
-
-                            List<String> cols = Lists.newArrayList();
-                            cols.add(pkg.getName());
- cols.add(locOptional.isPresent() ? locOptional.get().getSummary() : ""); - cols.add(pkg.getPkgPkgCategories().isEmpty() ? MARKER : "");
-
- for (String pkgCategoryCode : pkgCategoryCodes) { - cols.add(pkg.getPkgPkgCategory(pkgCategoryCode).isPresent() ? MARKER : "");
-                            }
-
- writer.writeNext(cols.toArray(new String[cols.size()]));
-
-                            return true;
-                        }
-                    });
-
-            LOGGER.info(
- "did produce category coverage spreadsheet report for {} packages in {}ms",
-                    count,
-                    System.currentTimeMillis() - startMs);
-
-        }
-
-    }
-
-}
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/model/PkgCategoryCoverageSpreadsheetJobSpecification.java Sat Dec 20 09:47:27 2014 UTC
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright 2014, Andrew Lindesay
- * Distributed under the terms of the MIT License.
- */
-
-package org.haikuos.haikudepotserver.pkg.model;
-
-import org.haikuos.haikudepotserver.job.model.AbstractJobSpecification;
-import org.haikuos.haikudepotserver.job.model.JobSpecification;
-import org.springframework.util.ObjectUtils;
-
-public class PkgCategoryCoverageSpreadsheetJobSpecification extends AbstractJobSpecification {
-
-    @Override
-    public boolean isEquivalent(JobSpecification other) {
- if(PkgCategoryCoverageSpreadsheetJobSpecification.class.isAssignableFrom(other.getClass())) { - return ObjectUtils.nullSafeEquals(other.getOwnerUserNickname(), getOwnerUserNickname());
-        }
-
-        return false;
-    }
-
-}
=======================================
--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/PkgApi.java Sat Dec 20 09:47:27 2014 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/PkgApi.java Wed Dec 31 10:32:57 2014 UTC
@@ -145,7 +145,7 @@
      * job.</p>
      */

- QueuePkgCategoryCoverageSpreadsheetJobResult queuePkgCategoryCoverageSpreadsheetJob(QueuePkgCategoryCoverageSpreadsheetJobRequest request); + QueuePkgCategoryCoverageExportSpreadsheetJobResult queuePkgCategoryCoverageExportSpreadsheetJob(QueuePkgCategoryCoverageExportSpreadsheetJobRequest request);

     /**
* <p>Enqueues a request on behalf of the current user to produce a spreadsheet showing which packages have icons
=======================================
--- /haikudepotserver-rpm-common/src/main/resources/config__config.properties Fri Oct 3 10:31:27 2014 UTC +++ /haikudepotserver-rpm-common/src/main/resources/config__config.properties Wed Dec 31 10:32:57 2014 UTC
@@ -9,6 +9,8 @@

 deployment.isproduction=false

+architecture.default.code=x86_gcc2
+
 # -------------------------------------------
 # database connection

=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/PkgApiImpl.java Sat Dec 20 09:47:27 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/PkgApiImpl.java Wed Dec 31 10:32:57 2014 UTC
@@ -29,10 +29,7 @@
 import org.haikuos.haikudepotserver.dataobjects.PkgVersionUrl;
 import org.haikuos.haikudepotserver.job.JobOrchestrationService;
 import org.haikuos.haikudepotserver.pkg.PkgOrchestrationService;
-import org.haikuos.haikudepotserver.pkg.model.PkgCategoryCoverageSpreadsheetJobSpecification; -import org.haikuos.haikudepotserver.pkg.model.PkgIconSpreadsheetJobSpecification; -import org.haikuos.haikudepotserver.pkg.model.PkgProminenceAndUserRatingSpreadsheetJobSpecification;
-import org.haikuos.haikudepotserver.pkg.model.PkgSearchSpecification;
+import org.haikuos.haikudepotserver.pkg.model.*;
 import org.haikuos.haikudepotserver.security.AuthorizationService;
 import org.haikuos.haikudepotserver.security.model.Permission;
 import org.haikuos.haikudepotserver.support.VersionCoordinates;
@@ -162,30 +159,7 @@
throw new ObjectNotFoundException(PkgCategory.class.getSimpleName(), null);
         }

- // now go through and delete any of those pkg relationships to packages that are already present - // and which are no longer required. Also remove those that we already have from the list.
-
- for(PkgPkgCategory pkgPkgCategory : ImmutableList.copyOf(pkg.getPkgPkgCategories())) {
-            if(!pkgCategories.contains(pkgPkgCategory.getPkgCategory())) {
- pkg.removeToManyTarget(Pkg.PKG_PKG_CATEGORIES_PROPERTY, pkgPkgCategory, true);
-                context.deleteObjects(pkgPkgCategory);
-            }
-            else {
-                pkgCategories.remove(pkgPkgCategory.getPkgCategory());
-            }
-        }
-
- // now any remaining in the pkgCategories will need to be added to the pkg.
-
-        for(PkgCategory pkgCategory : pkgCategories) {
- PkgPkgCategory pkgPkgCategory = context.newObject(PkgPkgCategory.class);
-            pkgPkgCategory.setPkgCategory(pkgCategory);
- pkg.addToManyTarget(Pkg.PKG_PKG_CATEGORIES_PROPERTY, pkgPkgCategory, true);
-        }
-
-        // now save and finish.
-
-        pkg.setModifyTimestamp();
+ pkgOrchestrationService.updatePkgCategories(context, pkg, pkgCategories);

         context.commitChanges();

@@ -1168,7 +1142,7 @@
     }

     @Override
- public QueuePkgCategoryCoverageSpreadsheetJobResult queuePkgCategoryCoverageSpreadsheetJob(QueuePkgCategoryCoverageSpreadsheetJobRequest request) { + public QueuePkgCategoryCoverageExportSpreadsheetJobResult queuePkgCategoryCoverageExportSpreadsheetJob(QueuePkgCategoryCoverageExportSpreadsheetJobRequest request) {
         Preconditions.checkArgument(null!=request);

         final ObjectContext context = serverRuntime.getContext();
@@ -1179,15 +1153,15 @@
                 context,
                 user.orNull(),
                 null,
-                Permission.BULK_PKGCATEGORYCOVERAGESPREADSHEETREPORT)) {
+                Permission.BULK_PKGCATEGORYCOVERAGEEXPORTSPREADSHEET)) {
LOGGER.warn("attempt to access a bulk category coverage spreadsheet report, but was unauthorized");
             throw new AuthorizationFailureException();
         }

- PkgCategoryCoverageSpreadsheetJobSpecification spec = new PkgCategoryCoverageSpreadsheetJobSpecification(); + PkgCategoryCoverageExportSpreadsheetJobSpecification spec = new PkgCategoryCoverageExportSpreadsheetJobSpecification();
         spec.setOwnerUserNickname(user.get().getNickname());

-        return new QueuePkgCategoryCoverageSpreadsheetJobResult(
+        return new QueuePkgCategoryCoverageExportSpreadsheetJobResult(
jobOrchestrationService.submit(spec,JobOrchestrationService.CoalesceMode.QUEUEDANDSTARTED).orNull());
     }

=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/PkgVersionLocalization.java Mon Jul 28 09:04:02 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/PkgVersionLocalization.java Wed Dec 31 10:32:57 2014 UTC
@@ -5,11 +5,59 @@

 package org.haikuos.haikudepotserver.dataobjects;

+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.exp.Expression;
+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._PkgVersionLocalization; import org.haikuos.haikudepotserver.dataobjects.support.CreateAndModifyTimestamped;
+import org.haikuos.haikudepotserver.support.cayenne.ExpressionHelper;
+
+import java.util.List;

public class PkgVersionLocalization extends _PkgVersionLocalization implements CreateAndModifyTimestamped {

+ public static Optional<PkgVersionLocalization> getAnyPkgVersionLocalizationForPkg(ObjectContext context, Pkg pkg) {
+
+        List<Expression> expressions = Lists.newArrayList();
+        String pvProp = PkgVersionLocalization.PKG_VERSION_PROPERTY;
+
+        expressions.add(ExpressionFactory.matchExp(
+                pvProp + "." + PkgVersion.PKG_PROPERTY + ".",
+                pkg));
+
+        expressions.add(ExpressionFactory.matchExp(
+                pvProp + "." + PkgVersion.IS_LATEST_PROPERTY,
+                true));
+
+        expressions.add(ExpressionFactory.matchExp(
+                pvProp + "." + PkgVersion.ACTIVE_PROPERTY,
+                true));
+
+ // the ordering is only important in order to ensure that integration tests are repeatable.
+
+        SelectQuery query = new SelectQuery(
+                PkgVersionLocalization.class,
+                ExpressionHelper.andAll(expressions),
+                ImmutableList.of(
+ new Ordering(pvProp + "." + PkgVersion.IS_LATEST_PROPERTY,SortOrder.DESCENDING), + new Ordering(pvProp + "." + PkgVersion.ARCHITECTURE_PROPERTY, SortOrder.DESCENDING)
+                ));
+
+ List<PkgVersionLocalization> locs = (List<PkgVersionLocalization>) context.performQuery(query);
+
+        if(locs.isEmpty()) {
+            return Optional.absent();
+        }
+
+        return Optional.of(locs.get(0));
+    }
+
     public boolean equalsForContent(PkgVersionLocalization other) {
         return
                 getSummary().equals(other.getSummary())
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/job/AbstractJobRunner.java Sat Dec 20 09:47:27 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/job/AbstractJobRunner.java Wed Dec 31 10:32:57 2014 UTC
@@ -6,6 +6,7 @@
 package org.haikuos.haikudepotserver.job;

 import org.haikuos.haikudepotserver.job.model.JobSpecification;
+import org.haikuos.haikudepotserver.job.model.JobRunnerException;

 import javax.annotation.Resource;
 import java.io.IOException;
@@ -36,6 +37,7 @@
     }

     @Override
- public abstract void run(JobOrchestrationService jobOrchestrationService, T specification) throws IOException; + public abstract void run(JobOrchestrationService jobOrchestrationService, T specification)
+            throws IOException, JobRunnerException;

 }
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/job/JobOrchestrationService.java Sat Dec 20 09:47:27 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/job/JobOrchestrationService.java Wed Dec 31 10:32:57 2014 UTC
@@ -26,6 +26,12 @@
         QUEUED,
         QUEUEDANDSTARTED
     }
+
+    /**
+ * <p>This method will block until the job is no longer queued or started.</p>
+     */
+
+    void awaitJobConcludedUninterruptibly(String guid, long timeout);

     Optional<String> submit(
             JobSpecification specification,
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/job/JobRunner.java Sat Dec 20 09:47:27 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/job/JobRunner.java Wed Dec 31 10:32:57 2014 UTC
@@ -6,6 +6,7 @@
 package org.haikuos.haikudepotserver.job;

 import org.haikuos.haikudepotserver.job.model.JobSpecification;
+import org.haikuos.haikudepotserver.job.model.JobRunnerException;

 import java.io.IOException;

@@ -24,6 +25,6 @@

     String getJobTypeCode();

- void run(JobOrchestrationService jobOrchestrationService, T specification) throws IOException; + void run(JobOrchestrationService jobOrchestrationService, T specification) throws IOException, JobRunnerException;

 }
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/job/LocalJobOrchestrationServiceImpl.java Sat Dec 20 09:47:27 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/job/LocalJobOrchestrationServiceImpl.java Wed Dec 31 10:32:57 2014 UTC
@@ -15,9 +15,7 @@
 import org.haikuos.haikudepotserver.support.DateTimeHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.beans.BeansException;
-import org.springframework.context.ApplicationContext;
-import org.springframework.context.ApplicationContextAware;
+import org.springframework.beans.factory.annotation.Autowired;

 import java.io.IOException;
 import java.io.InputStream;
@@ -34,7 +32,7 @@

 public class LocalJobOrchestrationServiceImpl
         extends AbstractService
-        implements ApplicationContextAware, JobOrchestrationService {
+        implements JobOrchestrationService {

protected static Logger LOGGER = LoggerFactory.getLogger(JobOrchestrationService.class);

@@ -48,11 +46,8 @@

private ArrayBlockingQueue<Runnable> runnables = Queues.newArrayBlockingQueue(SIZE_QUEUE);

-    /**
- * <p>Contains a mapping from the job type code to a suitable runner for that type code.</p>
-     */
-
-    private Map<String,JobRunner> jobRunners;
+    @Autowired
+    private List<JobRunner> jobRunners;

     /**
      * <p>Contains a mapping from the GUID to the job.</p>
@@ -66,16 +61,9 @@

     private Set<JobData> datas = Sets.newHashSet();

-    private ApplicationContext applicationContext;
-
public void setJobDataStorageService(JobDataStorageService dataStorageService) {
         this.dataStorageService = dataStorageService;
     }
-
-    @Override
- public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
-        this.applicationContext = applicationContext;
-    }

private synchronized Collection<Job> findJobsWithStatuses(final EnumSet<JobSnapshot.Status> statuses) { Preconditions.checkArgument(null!=statuses, "the status must be supplied to filter the job run states");
@@ -101,10 +89,55 @@
     // ------------------------------
     // RUN JOBS

-    private Optional<JobRunner> getJobRunner(String jobTypeCode) {
+    private boolean existsAndIsQueuedOrStarted(String guid) {
+ Optional<? extends JobSnapshot> jobSnapshotOptional = tryGetJob(guid);
+
+        if(!jobSnapshotOptional.isPresent()) {
+            return false;
+        }
+
+        switch(jobSnapshotOptional.get().getStatus()) {
+            case QUEUED:
+                case STARTED:
+                    return true;
+
+            default:
+                return false;
+        }
+
+    }
+
+    @Override
+ public void awaitJobConcludedUninterruptibly(String guid, long timeout) {
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(guid));
+        Preconditions.checkArgument(timeout > 0);
+        long ms = System.currentTimeMillis();
+
+        while(System.currentTimeMillis() - ms < timeout
+                && existsAndIsQueuedOrStarted(guid)) {
+            synchronized(this) {
+                try {
+                    wait(timeout);
+                }
+                catch(InterruptedException ie) {
+                    Thread.currentThread().interrupt();
+                }
+            }
+        }
+    }
+
+    private Optional<JobRunner> getJobRunner(final String jobTypeCode) {
         Preconditions.checkArgument(!Strings.isNullOrEmpty(jobTypeCode));
Preconditions.checkState(null!=jobRunners,"the job runners must be configured - was this started up properly?");
-        return Optional.fromNullable(jobRunners.get(jobTypeCode));
+        return Iterables.tryFind(
+                jobRunners,
+                new Predicate<JobRunner>() {
+                    @Override
+                    public boolean apply(JobRunner input) {
+                        return input.getJobTypeCode().equals(jobTypeCode);
+                    }
+                }
+        );
     }

     private Job submit(JobSpecification specification) {
@@ -449,6 +482,8 @@

         job.setStartTimestamp();
         LOGGER.info("{}; start", job.getJobSpecification().toString());
+
+        notifyAll();
     }

     public synchronized void setJobFinishTimestamp(String guid) {
@@ -460,6 +495,8 @@

         job.setFinishTimestamp();
         LOGGER.info("{}; finish", job.getJobSpecification().toString());
+
+        notifyAll();
     }

     public synchronized void setJobRunQueuedTimestamp(String guid) {
@@ -471,6 +508,8 @@

         job.setQueuedTimestamp();
         LOGGER.info("{}; queued", job.getJobSpecification().toString());
+
+        notifyAll();
     }

     @Override
@@ -483,6 +522,8 @@

         job.setFailTimestamp();
         LOGGER.info("{}; fail", job.getJobSpecification().toString());
+
+        notifyAll();
     }

     @Override
@@ -494,6 +535,7 @@
             case STARTED:
                 job.setCancelTimestamp();
LOGGER.info("{}; cancelled", job.getJobSpecification().toString());
+                notifyAll();
                 break;

             default:
@@ -519,6 +561,8 @@
         }

         job.setProgressPercent(progressPercent);
+
+        notifyAll();
     }

     // ------------------------------
@@ -532,16 +576,6 @@
             LOGGER.info("will start service");

             jobs = Maps.newHashMap();
-
-            jobRunners = Maps.newHashMap();
-
- for(JobRunner jobRunner : applicationContext.getBeansOfType(JobRunner.class).values()) {
-                jobRunners.put(jobRunner.getJobTypeCode(), jobRunner);
-                LOGGER.info(
-                        "registered job runner; {} ({})",
-                        jobRunner.getJobTypeCode(),
-                        jobRunner.getClass().getSimpleName());
-            }

             executor = new ThreadPoolExecutor(
                     0, // core pool size
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/job/controller/JobController.java Sat Dec 20 09:47:27 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/job/controller/JobController.java Wed Dec 31 10:32:57 2014 UTC
@@ -6,7 +6,9 @@
 package org.haikuos.haikudepotserver.job.controller;

 import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
+import com.google.common.io.ByteSource;
 import com.google.common.net.HttpHeaders;
 import com.google.common.net.MediaType;
 import org.apache.cayenne.ObjectContext;
@@ -23,14 +25,13 @@
 import org.slf4j.LoggerFactory;
 import org.springframework.http.HttpStatus;
 import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.*;

 import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
+import java.io.InputStream;

 /**
* <p>The job controller allows for upload and download of binary data related to jobs; for example, there are
@@ -46,6 +47,8 @@

     public final static String KEY_GUID = "guid";

+    public final static String KEY_USECODE = "usecode";
+
     @Resource
     private JobOrchestrationService jobOrchestrationService;

@@ -55,8 +58,42 @@
     @Resource
     private AuthorizationService authorizationService;

+    /**
+ * <p>This URL can be used to supply data that can be used with a job to be run as an input to the
+     * job.</p>
+     */
+
+    @RequestMapping(value = "/jobdata", method = RequestMethod.POST)
+    @ResponseBody
+    public String supplyData(
+            final HttpServletRequest request,
+ @RequestHeader(value = HttpHeaders.CONTENT_TYPE, required = false) String contentType, + @RequestParam(value = KEY_USECODE, required = false) String useCode)
+    throws IOException {
+
+        Preconditions.checkArgument(null!=request);
+        assert null!=request;
+
+        JobData data = jobOrchestrationService.storeSuppliedData(
+                useCode,
+ !Strings.isNullOrEmpty(contentType) ? contentType : MediaType.OCTET_STREAM.toString(),
+                new ByteSource() {
+                    @Override
+                    public InputStream openStream() throws IOException {
+                        return request.getInputStream();
+                    }
+                }
+        );
+
+        return data.getGuid();
+    }
+
+    /**
+ * <p>This URL can be used to download job data that has resulted from a job being run.</p>
+     */
+
@RequestMapping(value = "/jobdata/{" + KEY_GUID + "}/download", method = RequestMethod.GET)
-    public void data(
+    public void downloadGeneratedData(
             HttpServletResponse response,
             @PathVariable(value = KEY_GUID) String guid)
     throws IOException{
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/PkgOrchestrationService.java Wed Nov 26 09:34:57 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/PkgOrchestrationService.java Wed Dec 31 10:32:57 2014 UTC
@@ -1194,5 +1194,54 @@
         return Collections.emptyList();
     }

+    // ------------------------------
+    // MISC
+
+    /**
+ * <p>This method will update the {@link org.haikuos.haikudepotserver.dataobjects.PkgCategory} set in the + * nominated {@link org.haikuos.haikudepotserver.dataobjects.Pkg} such that the supplied set are the + * categories for the package. It will do this by adding and removing relationships between the package
+     * and the categories.</p>
+     * @return true if a change was made.
+     */
+
+ public boolean updatePkgCategories(ObjectContext context, Pkg pkg, List<PkgCategory> pkgCategories) {
+        Preconditions.checkArgument(null!=context);
+        Preconditions.checkArgument(null!=pkg);
+        Preconditions.checkArgument(null!=pkgCategories);
+
+        boolean didChange = false;
+
+ // now go through and delete any of those pkg relationships to packages that are already present + // and which are no longer required. Also remove those that we already have from the list.
+
+ for(PkgPkgCategory pkgPkgCategory : ImmutableList.copyOf(pkg.getPkgPkgCategories())) {
+            if(!pkgCategories.contains(pkgPkgCategory.getPkgCategory())) {
+ pkg.removeToManyTarget(Pkg.PKG_PKG_CATEGORIES_PROPERTY, pkgPkgCategory, true);
+                context.deleteObjects(pkgPkgCategory);
+                didChange = true;
+            }
+            else {
+                pkgCategories.remove(pkgPkgCategory.getPkgCategory());
+            }
+        }
+
+ // now any remaining in the pkgCategories will need to be added to the pkg.
+
+        for(PkgCategory pkgCategory : pkgCategories) {
+ PkgPkgCategory pkgPkgCategory = context.newObject(PkgPkgCategory.class);
+            pkgPkgCategory.setPkgCategory(pkgCategory);
+ pkg.addToManyTarget(Pkg.PKG_PKG_CATEGORIES_PROPERTY, pkgPkgCategory, true);
+            didChange = true;
+        }
+
+        // now save and finish.
+
+        if(didChange) {
+            pkg.setModifyTimestamp();
+        }
+
+        return didChange;
+    }

 }
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthorizationService.java Sun Dec 7 09:15:14 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/AuthorizationService.java Wed Dec 31 10:32:57 2014 UTC
@@ -218,9 +218,12 @@

             case BULK_PKGPROMINENCEANDUSERRATINGSPREADSHEETREPORT:
             case BULK_PKGICONSPREADSHEETREPORT:
-            case BULK_PKGCATEGORYCOVERAGESPREADSHEETREPORT:
+            case BULK_PKGCATEGORYCOVERAGEEXPORTSPREADSHEET:
                 return null!=authenticatedUser;

+            case BULK_PKGCATEGORYCOVERAGEIMPORTSPREADSHEET:
+ return null!=authenticatedUser && authenticatedUser.getIsRoot();
+
             case BULK_USERRATINGSPREADSHEETREPORT_ALL:
return null!=authenticatedUser && authenticatedUser.getIsRoot();

=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/model/Permission.java Sun Dec 7 09:15:14 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/security/model/Permission.java Wed Dec 31 10:32:57 2014 UTC
@@ -33,7 +33,8 @@
     PKG_EDITVERSIONLOCALIZATION(TargetType.PKG),
     PKG_EDITPROMINENCE(TargetType.PKG),

-    BULK_PKGCATEGORYCOVERAGESPREADSHEETREPORT(null),
+    BULK_PKGCATEGORYCOVERAGEEXPORTSPREADSHEET(null),
+    BULK_PKGCATEGORYCOVERAGEIMPORTSPREADSHEET(null),
     BULK_PKGPROMINENCEANDUSERRATINGSPREADSHEETREPORT(null),
     BULK_PKGICONSPREADSHEETREPORT(null),
     BULK_USERRATINGSPREADSHEETREPORT_PKG(TargetType.PKG),
=======================================
--- /haikudepotserver-webapp/src/main/resources/messages.properties Sun Dec 21 08:33:27 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/messages.properties Wed Dec 31 10:32:57 2014 UTC
@@ -411,7 +411,7 @@

 reporting.didreject.description=The report couldn't be enqueued; \
   possibly because there is an equivalent report running already.
-reporting.pkgcategorycoveragespreadsheetreport.title=Package category coverage report +reporting.pkgcategorycoverageexportspreadsheet.title=Package category coverage report reporting.pkgprominenceanduserratingspreadsheetreport.title=Package prominence and user rating report
 reporting.pkgiconspreadsheetreport.title=Package icon report
 reporting.userratingspreadsheetreportall.title=Report of all user ratings
@@ -424,7 +424,7 @@
 job.jobtype.passwordresetmaintenance.title=Password Reset Maintenance
 job.jobtype.pkgrepositoryimport.title=Package Repository Import
job.jobtype.pkgprominenceanduserratingspreadsheet.title=Package Prominence and User Ratings Spreadsheet -job.jobtype.pkgcategorycoveragespreadsheet.title=Package Category Coverage Spreadsheet +job.jobtype.pkgcategorycoverageexportspreadsheet.title=Package Category Coverage Spreadsheet
 job.jobtype.pkgiconspreadsheet.title=Package Icons Spreadsheet
 job.jobtype.userratingspreadsheet.title=User Rating Spreadsheet

=======================================
--- /haikudepotserver-webapp/src/main/resources/messages_de.properties Sun Dec 21 08:33:27 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/messages_de.properties Wed Dec 31 10:32:57 2014 UTC
@@ -403,7 +403,7 @@
   die entsprechende URL erzeugen, die dann mit einer Feed-Sammel-Software \
   genutzt werden kann.

-reporting.pkgcategorycoveragespreadsheetreport.title=Verteilung der Pakete nach Kategorien +reporting.pkgcategorycoverageexportspreadsheet.title=Verteilung der Pakete nach Kategorien reporting.pkgprominenceanduserratingspreadsheetreport.title=Empfehlungsstufen und Bewertungen der Pakete
 reporting.pkgiconspreadsheetreport.title=Icons der Pakete
 reporting.userratingspreadsheetreportall.title=Alle Bewertungen
@@ -412,7 +412,7 @@
 job.jobtype.passwordresetmaintenance.title=Kennwort zurücksetzen
 job.jobtype.pkgrepositoryimport.title=Depots-Import
job.jobtype.pkgprominenceanduserratingspreadsheet.title=Tabelle der Empfehlungsstufen und Paket-Bewertungen -job.jobtype.pkgcategorycoveragespreadsheet.title=Tabelle der Verteilung der Pakete nach Kategorien +job.jobtype.pkgcategorycoverageexportspreadsheet.title=Tabelle der Verteilung der Pakete nach Kategorien
 job.jobtype.pkgiconspreadsheet.title=Tabelle der Paket-Icons
 job.jobtype.userratingspreadsheet.title=Tabelle aller Bewertungen

=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/constants.js Sun Dec 7 09:15:14 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/constants.js Wed Dec 31 10:32:57 2014 UTC
@@ -14,7 +14,7 @@

         RECENT_DAYS : 90,

-        ARCHITECTURE_CODE_DEFAULT : 'x86',
+        ARCHITECTURE_CODE_DEFAULT : 'x86_gcc2',

         ENDPOINT_API_V1_REPOSITORY : '/api/v1/repository',
         ENDPOINT_API_V1_PKG : '/api/v1/pkg',
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/homecontroller.js Wed Sep 3 09:55:22 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/homecontroller.js Wed Dec 31 10:32:57 2014 UTC
@@ -173,6 +173,10 @@
                             if($location.search()[KEY_ARCHITECTURECODE]) {
$scope.selectedArchitecture = _.findWhere(data,{ code : $location.search()[KEY_ARCHITECTURECODE] });
                             }
+
+                            if(!$scope.selectedArchitecture) {
+ $scope.selectedArchitecture = _.findWhere(data,{ code : constants.ARCHITECTURE_CODE_DEFAULT });
+                            }

                             if(!$scope.selectedArchitecture) {
$scope.selectedArchitecture = $scope.architectures[0];
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/reports.html Sat Dec 20 09:47:27 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/reports.html Wed Dec 31 10:32:57 2014 UTC
@@ -7,9 +7,9 @@
 <div class="content-container">

     <ul>
- <li show-if-permission="'BULK_PKGCATEGORYCOVERAGESPREADSHEETREPORT'">
-            <a href="" ng-click="goPkgCategoryCoverageSpreadsheetReport()">
- <message key="reporting.pkgcategorycoveragespreadsheetreport.title"></message> + <li show-if-permission="'BULK_PKGCATEGORYCOVERAGEEXPORTSPREADSHEET'">
+            <a href="" ng-click="goPkgCategoryCoverageExportSpreadsheet()">
+ <message key="reporting.pkgcategorycoverageexportspreadsheet.title"></message>
             </a>
         </li>
<li show-if-permission="'BULK_PKGPROMINENCEANDUSERRATINGSPREADSHEETREPORT'">
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/reportscontroller.js Sat Dec 20 09:47:27 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/reportscontroller.js Wed Dec 31 10:32:57 2014 UTC
@@ -73,8 +73,8 @@
                 );
             }

-            $scope.goPkgCategoryCoverageSpreadsheetReport = function() {
-                goBasicPkgReport('queuePkgCategoryCoverageSpreadsheetJob');
+            $scope.goPkgCategoryCoverageExportSpreadsheet = function() {
+ goBasicPkgReport('queuePkgCategoryCoverageExportSpreadsheetJob');
             };

$scope.goPkgProminenceAndUserRatingSpreadsheetReport = function() {
=======================================
--- /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotserver/AbstractIntegrationTest.java Sat Dec 20 09:47:27 2014 UTC +++ /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotserver/AbstractIntegrationTest.java Wed Dec 31 10:32:57 2014 UTC
@@ -7,7 +7,8 @@

 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
-import com.google.common.io.ByteStreams;
+import com.google.common.io.ByteSource;
+import junit.framework.Assert;
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.ObjectId;
 import org.apache.cayenne.configuration.server.ServerRuntime;
@@ -24,6 +25,7 @@
 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

 import javax.annotation.Resource;
+import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.sql.Connection;
@@ -31,6 +33,7 @@
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.Map;
+import java.util.Objects;

 /**
* <p>This superclass of all of the tests has a hook to run before each integration test. The hook will
@@ -63,16 +66,39 @@
     @Resource
     protected CapturingMailSender mailSender;

-    protected byte[] getResourceData(String path) throws IOException {
+ protected void assertEqualsLineByLine(BufferedReader expected, BufferedReader actual) throws IOException {
+            String expectedLine;
+            String actualLine;

- try (InputStream inputStream = this.getClass().getResourceAsStream(path)) {
+            do {
+                expectedLine = expected.readLine();
+                actualLine = actual.readLine();

-            if(null==inputStream) {
- throw new IllegalStateException("unable to find the test resource; "+path);
+                if(!Objects.equals(expectedLine, actualLine)) {
+ Assert.fail("mismatch expected and actual; [" + expectedLine + "] [" + actualLine + "]");
+                }
+
             }
+            while(null!=expectedLine || null!=actualLine);
+    }
+
+    protected ByteSource getResourceByteSource(final String path) {
+        return new ByteSource() {
+            @Override
+            public InputStream openStream() throws IOException {
+ InputStream result = this.getClass().getResourceAsStream(path);
+
+                if (null == result) {
+ throw new IllegalStateException("unable to find the test resource; " + path);
+                }
+
+                return result;
+            }
+        };
+    }

-            return ByteStreams.toByteArray(inputStream);
-        }
+    protected byte[] getResourceData(String path) throws IOException {
+        return getResourceByteSource(path).read();
     }

private String getDatabaseName(Connection connection) throws SQLException {
=======================================
--- /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotserver/job/TestJobOrchestrationServiceImpl.java Sat Dec 20 09:47:27 2014 UTC +++ /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotserver/job/TestJobOrchestrationServiceImpl.java Wed Dec 31 10:32:57 2014 UTC
@@ -197,4 +197,10 @@
public Optional<JobDataWithByteSource> tryObtainData(String guid) throws IOException {
         return Optional.absent();
     }
+
+    @Override
+ public void awaitJobConcludedUninterruptibly(String guid, long timeout) {
+        // ignore
+    }
+
 }

==============================================================================
Revision: 8f577ae37799
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Fri Jan  2 10:56:12 2015 UTC
Log: introduce a user interface for the package category coverage import process

https://code.google.com/p/haiku-depot-web-app/source/detail?r=8f577ae37799

Added:
/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/QueuePkgCategoryCoverageImportSpreadsheetJobRequest.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/QueuePkgCategoryCoverageImportSpreadsheetJobResult.java /haikudepotserver-webapp/src/main/webapp/js/app/controller/pkgcategorycoverageimportspreadsheet.html /haikudepotserver-webapp/src/main/webapp/js/app/controller/pkgcategorycoverageimportspreadsheetcontroller.js
 /haikudepotserver-webapp/src/main/webapp/js/app/service/jobsservice.js
Modified:
/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/PkgApi.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/PkgApiImpl.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/job/LocalJobOrchestrationServiceImpl.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/job/controller/JobController.java
 /haikudepotserver-webapp/src/main/resources/messages.properties
/haikudepotserver-webapp/src/main/webapp/js/app/controller/rootoperations.html /haikudepotserver-webapp/src/main/webapp/js/app/controller/rootoperationscontroller.js
 /haikudepotserver-webapp/src/main/webapp/js/app/routes.js
/haikudepotserver-webapp/src/main/webapp/js/app/service/breadcrumbfactoryservice.js
 /haikudepotserver-webapp/src/main/webapp/js/app/service/userstateservice.js

=======================================
--- /dev/null
+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/QueuePkgCategoryCoverageImportSpreadsheetJobRequest.java Fri Jan 2 10:56:12 2015 UTC
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2015, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.pkg;
+
+public class QueuePkgCategoryCoverageImportSpreadsheetJobRequest {
+
+    public String inputDataGuid;
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/QueuePkgCategoryCoverageImportSpreadsheetJobResult.java Fri Jan 2 10:56:12 2015 UTC
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2015, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.pkg;
+
+public class QueuePkgCategoryCoverageImportSpreadsheetJobResult {
+
+    public String guid;
+
+    public QueuePkgCategoryCoverageImportSpreadsheetJobResult() {
+    }
+
+ public QueuePkgCategoryCoverageImportSpreadsheetJobResult(String guid) {
+        this.guid = guid;
+    }
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/pkgcategorycoverageimportspreadsheet.html Fri Jan 2 10:56:12 2015 UTC
@@ -0,0 +1,29 @@
+<breadcrumbs></breadcrumbs>
+
+<div class="content-container">
+
+        <form name="specificationForm" novalidate="novalidate">
+
+ <label for="import-data-file"><message key="pkgCategoryCoverageImportSpreadsheet.importDataFile.title"></message></label> + <div class="form-control-group" ng-class="deriveFormControlsContainerClasses('importDataFile')"> + <input id="import-data-file" name="importDataFile" type="file" ng-model="specification.importDataFile" file-supply required></input> + <error-messages key-prefix="pkgCategoryCoverageImportSpreadsheet.importDataFile" error="specificationForm.importDataFile.$error"></error-messages>
+            </div>
+
+            <div class="form-action-container">
+                <button
+                        ng-disabled="specificationForm.$invalid"
+                        ng-click="goQueue()"
+                        type="submit"
+                        class="main-action">
+ <message key="pkgCategoryCoverageImportSpreadsheet.action.title"></message>
+                </button>
+            </div>
+
+        </form>
+
+</div>
+
+<div class="footer"></div>
+<spinner spin="shouldSpin()"></spinner>
+
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/pkgcategorycoverageimportspreadsheetcontroller.js Fri Jan 2 10:56:12 2015 UTC
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2015, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+angular.module('haikudepotserver').controller(
+    'PkgCategoryCoverageImportSpreadsheetController',
+    [
+        '$scope','$log','$location','$routeParams',
+        'jsonRpc','constants','pkgIcon','errorHandling',
+        'breadcrumbs','breadcrumbFactory','pkg','jobs',
+        function(
+            $scope,$log,$location,$routeParams,
+            jsonRpc,constants,pkgIcon,errorHandling,
+            breadcrumbs,breadcrumbFactory,pkg,jobs) {
+
+            var IMPORT_SIZE_LIMIT = 256 * 1024; // 100k
+
+            $scope.specification = {
+                importDataFile : undefined
+            };
+
+            $scope.amQueueing = false;
+
+            $scope.shouldSpin = function() {
+                return $scope.amQueueing;
+            };
+
+            $scope.deriveFormControlsContainerClasses = function(name) {
+ return $scope.specificationForm[name].$invalid ? ['form-control-group-error'] : [];
+            };
+
+            function refreshBreadcrumbItems() {
+                breadcrumbs.mergeCompleteStack([
+                    breadcrumbFactory.createHome(),
+                    breadcrumbFactory.createRootOperations(),
+ breadcrumbFactory.createPkgCategoryCoverageImportSpreadsheet()
+                ]);
+            }
+
+            refreshBreadcrumbItems();
+
+ // This function will check to make sure that the file is not too large or too small to be a valid
+            // input for this importation process.
+
+            function validateImportDataFile(file, model) {
+ model.$setValidity('badsize',undefined==file || (file.size
24 && file.size < IMPORT_SIZE_LIMIT));
+            }
+
+            function importDataFileDidChange() {
+ validateImportDataFile($scope.specification.importDataFile, $scope.specificationForm['importDataFile']);
+            }
+
+            $scope.$watch('specification.importDataFile', function() {
+                importDataFileDidChange();
+            });
+
+
+ // This function will take the data from the form and load in the new pkg icons
+
+            $scope.goQueue = function() {
+
+                if($scope.specificationForm.$invalid) {
+ throw Error('expected the import of package categories only to be possible if the form is valid');
+                }
+
+                $scope.amQueueing = true;
+
+ // uploads the import data to the server so it can be used later.
+
+                jobs.supplyData($scope.specification.importDataFile).then(
+                    function(guid) {
+ $log.info('did upload import data to the server; ' + guid);
+
+                        jsonRpc.call(
+                            constants.ENDPOINT_API_V1_PKG,
+                            "queuePkgCategoryCoverageImportSpreadsheetJob",
+                            [{ inputDataGuid: guid }]
+                        ).then(
+                            function(result) {
+ $log.info('did queue pkg category coverage import spreadsheet job; ' + result.guid); + breadcrumbs.pushAndNavigate(breadcrumbFactory.createViewJob({ guid:result.guid }));
+                                $scope.amQueueing = false;
+                            },
+                            function(err) {
+                                $scope.amQueueing = false;
+                                errorHandling.handleJsonRpcError(err);
+                            }
+                        );
+
+                    },
+                    function() {
+ $log.error('failed to upload import data to the server');
+                        errorHandling.navigateToError();
+                        $scope.amQueueing = false;
+                    }
+                );
+
+            }; // goQueue
+
+        }
+    ]
+);
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/service/jobsservice.js Fri Jan 2 10:56:12 2015 UTC
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2015, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+/**
+ * <p>This service provides functionality for accessing and updating job data.</p>
+ */
+
+angular.module('haikudepotserver').factory('jobs',
+    [
+        '$log','$q','$http',
+        function($log,$q,$http) {
+
+            var errorCodes = {
+                BADREQUEST : 400,
+                UNKNOWN : -1
+            };
+
+            var headers = {};
+
+            function setHeader(name, value) {
+                if(!name || !name.length) {
+                    throw Error('the name of the http header is required');
+                }
+
+                if(!value || !value.length) {
+                    delete headers[name];
+                }
+                else {
+                    headers[name] = value;
+                }
+            }
+
+            /**
+ * <p>Uploads the file so that it can be used as input for a job. It will return a GUID reference to the
+             * data if the upload was successful.</p>
+             */
+
+            function supplyData(file) {
+
+                if(!file) {
+ throw Error('the file must be supplied to provide data for the upload.');
+                }
+
+                var deferred = $q.defer();
+
+                $http({
+                    cache: false,
+                    method: 'POST',
+                    url: '/secured/jobdata',
+ headers: _.extend({ 'Content-Type' : 'application/octet-stream' },headers),
+                    data: file
+                })
+                    .success(function(data,status,header,config) {
+                        var code = header('X-HaikuDepotServer-DataGuid');
+
+                        if(!code || !code.length) {
+ throw Error('the data guid should have been supplied back from supplying data');
+                        }
+
+                        deferred.resolve(code);
+                    })
+                    .error(function(data,status) {
+                        switch(status) {
+                            case 200:
+                                deferred.resolve();
+                                break;
+
+                            case 400: // bad request
+                                deferred.reject(errorCodes.BADREQUEST);
+                                break;
+
+                            default:
+                                deferred.reject(errorCodes.UNKNOWN);
+                                break;
+
+                        }
+                    });
+
+                return deferred.promise;
+
+            }
+
+            return {
+
+ // these are errors that may be returned to the caller below. They match to the HTTP status codes
+                // used, but this should not be relied upon.
+
+                errorCodes : errorCodes,
+
+                setHeader : function(name, value) {
+                    setHeader(name, value);
+                },
+
+ // this function will upload the data from the supplied file to the server and will return a GUID + // that acts as a token to represent the data for subsequent API calls. The returned data is
+                // provided via a promise.
+
+                supplyData : function(file) {
+                    return supplyData(file);
+                }
+
+            };
+
+        }
+    ]
+);
=======================================
--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/PkgApi.java Wed Dec 31 10:32:57 2014 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/PkgApi.java Fri Jan 2 10:56:12 2015 UTC
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013-2014, Andrew Lindesay
+ * Copyright 2013-2015, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

@@ -147,6 +147,14 @@

QueuePkgCategoryCoverageExportSpreadsheetJobResult queuePkgCategoryCoverageExportSpreadsheetJob(QueuePkgCategoryCoverageExportSpreadsheetJobRequest request);

+    /**
+ * <P>Enqueues a request on behalf od the current user to import package data from a spreadsheet that is uploaded + * to the server. It does this and also produces an outbound spreadsheet of the result.</P> + * @throws org.haikuos.haikudepotserver.api1.support.ObjectNotFoundException in the case that the data identified by GUID does not exist.
+     */
+
+ QueuePkgCategoryCoverageImportSpreadsheetJobResult queuePkgCategoryCoverageImportSpreadsheetJob(QueuePkgCategoryCoverageImportSpreadsheetJobRequest request) throws ObjectNotFoundException;
+
     /**
* <p>Enqueues a request on behalf of the current user to produce a spreadsheet showing which packages have icons * associated with them. See the {@link org.haikuos.haikudepotserver.api1.JobApi} for details on how to control the
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/PkgApiImpl.java Wed Dec 31 10:32:57 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/PkgApiImpl.java Fri Jan 2 10:56:12 2015 UTC
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013-2014, Andrew Lindesay
+ * Copyright 2013-2015, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

@@ -28,6 +28,7 @@
 import org.haikuos.haikudepotserver.dataobjects.PkgVersionLocalization;
 import org.haikuos.haikudepotserver.dataobjects.PkgVersionUrl;
 import org.haikuos.haikudepotserver.job.JobOrchestrationService;
+import org.haikuos.haikudepotserver.job.model.JobData;
 import org.haikuos.haikudepotserver.pkg.PkgOrchestrationService;
 import org.haikuos.haikudepotserver.pkg.model.*;
 import org.haikuos.haikudepotserver.security.AuthorizationService;
@@ -1154,7 +1155,7 @@
                 user.orNull(),
                 null,
                 Permission.BULK_PKGCATEGORYCOVERAGEEXPORTSPREADSHEET)) {
- LOGGER.warn("attempt to access a bulk category coverage spreadsheet report, but was unauthorized"); + LOGGER.warn("attempt to access a package category coverage export spreadsheet, but was unauthorized");
             throw new AuthorizationFailureException();
         }

@@ -1164,6 +1165,43 @@
         return new QueuePkgCategoryCoverageExportSpreadsheetJobResult(
jobOrchestrationService.submit(spec,JobOrchestrationService.CoalesceMode.QUEUEDANDSTARTED).orNull());
     }
+
+    @Override
+ public QueuePkgCategoryCoverageImportSpreadsheetJobResult queuePkgCategoryCoverageImportSpreadsheetJob( + QueuePkgCategoryCoverageImportSpreadsheetJobRequest request) throws ObjectNotFoundException { + Preconditions.checkArgument(null!=request,"the request must be supplied"); + Preconditions.checkArgument(!Strings.isNullOrEmpty(request.inputDataGuid), "the input data must be identified by guid");
+
+        final ObjectContext context = serverRuntime.getContext();
+
+        Optional<User> user = tryObtainAuthenticatedUser(context);
+
+        if(!authorizationService.check(
+                context,
+                user.orNull(),
+                null,
+                Permission.BULK_PKGCATEGORYCOVERAGEIMPORTSPREADSHEET)) {
+ LOGGER.warn("attempt to import package categories, but was not authorized");
+            throw new AuthorizationFailureException();
+        }
+
+        // now check that the data is present.
+
+ Optional<JobData> dataOptional = jobOrchestrationService.tryGetData(request.inputDataGuid);
+
+        if(!dataOptional.isPresent()) {
+ throw new ObjectNotFoundException(JobData.class.getSimpleName(), request.inputDataGuid);
+        }
+
+        // setup and go
+
+ PkgCategoryCoverageImportSpreadsheetJobSpecification spec = new PkgCategoryCoverageImportSpreadsheetJobSpecification();
+        spec.setOwnerUserNickname(user.get().getNickname());
+        spec.setInputDataGuid(request.inputDataGuid);
+
+        return new QueuePkgCategoryCoverageImportSpreadsheetJobResult(
+ jobOrchestrationService.submit(spec, JobOrchestrationService.CoalesceMode.NONE).orNull());
+    }

     @Override
public QueuePkgIconSpreadsheetJobResult queuePkgIconSpreadsheetJob(QueuePkgIconSpreadsheetJobRequest request) {
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/job/LocalJobOrchestrationServiceImpl.java Wed Dec 31 10:32:57 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/job/LocalJobOrchestrationServiceImpl.java Fri Jan 2 10:56:12 2015 UTC
@@ -776,6 +776,9 @@
         long len;

         try(InputStream inputStream = byteSource.openStream()) {
+
+            // TODO; constrain this to a sensible size
+
             len = dataStorageService.put(guid).writeFrom(inputStream);
data = new JobData(guid, JobDataType.SUPPLIED, useCode, mediaTypeCode);

=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/job/controller/JobController.java Wed Dec 31 10:32:57 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/job/controller/JobController.java Fri Jan 2 10:56:12 2015 UTC
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014, Andrew Lindesay
+ * Copyright 2014-2015, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

@@ -43,6 +43,10 @@
 @RequestMapping("/secured")
 public class JobController extends AbstractController {

+ private final static long MAX_SUPPLY_DATA_LENGTH = 1 * 1024 * 1024; // 1MB
+
+ public final static String HEADER_DATAGUID = "X-HaikuDepotServer-DataGuid";
+
protected static Logger LOGGER = LoggerFactory.getLogger(JobController.class);

     public final static String KEY_GUID = "guid";
@@ -65,14 +69,30 @@

     @RequestMapping(value = "/jobdata", method = RequestMethod.POST)
     @ResponseBody
-    public String supplyData(
+    public void supplyData(
             final HttpServletRequest request,
+            final HttpServletResponse response,
@RequestHeader(value = HttpHeaders.CONTENT_TYPE, required = false) String contentType, @RequestParam(value = KEY_USECODE, required = false) String useCode)
     throws IOException {

         Preconditions.checkArgument(null!=request);
         assert null!=request;
+
+        int length = request.getContentLength();
+
+        if(-1 != length && length > MAX_SUPPLY_DATA_LENGTH) {
+            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+        }
+
+        ObjectContext context = serverRuntime.getContext();
+
+        Optional<User> user = tryObtainAuthenticatedUser(context);
+
+        if(!user.isPresent()) {
+ LOGGER.warn("attempt to supply job data with no authenticated user");
+            throw new JobDataAuthorizationFailure();
+        }

         JobData data = jobOrchestrationService.storeSuppliedData(
                 useCode,
@@ -85,7 +105,8 @@
                 }
         );

-        return data.getGuid();
+        response.setStatus(HttpServletResponse.SC_OK);
+        response.setHeader(HEADER_DATAGUID, data.getGuid());
     }

     /**
=======================================
--- /haikudepotserver-webapp/src/main/resources/messages.properties Wed Dec 31 10:32:57 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/messages.properties Fri Jan 2 10:56:12 2015 UTC
@@ -54,6 +54,7 @@
 breadcrumb.reports.title=Reports
 breadcrumb.listJobs.title=List Jobs
 breadcrumb.viewJob.title=View Job
+breadcrumb.pkgCategoryCoverageImportSpreadsheet.title=Import Pkg Categories

 about.info.title=Information
 about.info.version=Version {0}
@@ -346,6 +347,11 @@
exists. It may be that the exact same rule was already present or it may be that a less \
   specific rule already existed.

+pkgCategoryCoverageImportSpreadsheet.importDataFile.title=Import Data
+pkgCategoryCoverageImportSpreadsheet.importDataFile.required=The import data is required to import from.
+pkgCategoryCoverageImportSpreadsheet.action.title=Import
+
+
 banner.action.more=About 'Haiku Depot Server'
 banner.action.authenticate=User login
 banner.action.createUser=Register new user
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/rootoperations.html Tue Dec 9 09:53:59 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/rootoperations.html Fri Jan 2 10:56:12 2015 UTC
@@ -28,6 +28,7 @@
<li><a href="" ng-click="goPaginationControlPlayground()">Pagination playground</a></li> <li><a href="" ng-click="goRaiseExceptionInLocalRuntime()">Raise test exception in javascript environment</a></li> <li><a href="" ng-click="goRaiseExceptionInServerRuntime()">Raise test exception on server via json-rpc</a></li> + <li><a href="" ng-click="goPkgCategoryCoverageImportSpreadsheet()">Import package categories</a></li>
         <li><a href="" ng-click="goJobs()">List jobs</a></li>
     </ul>

=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/rootoperationscontroller.js Sun Dec 7 09:15:14 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/rootoperationscontroller.js Fri Jan 2 10:56:12 2015 UTC
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014, Andrew Lindesay
+ * Copyright 2014-2015, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

@@ -94,6 +94,10 @@
breadcrumbs.pushAndNavigate(breadcrumbFactory.createListJobs());
             };

+            $scope.goPkgCategoryCoverageImportSpreadsheet = function() {
+ breadcrumbs.pushAndNavigate(breadcrumbFactory.createPkgCategoryCoverageImportSpreadsheet());
+            };
+
             // -------------------
             // TEST ERROR HANDLING TESTING

=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/routes.js Tue Dec 9 09:49:00 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/routes.js Fri Jan 2 10:56:12 2015 UTC
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013-2014, Andrew Lindesay
+ * Copyright 2013-2015, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

@@ -13,6 +13,7 @@
             $routeProvider
                 
.when('/rootoperations',{controller:'RootOperationsController',templateUrl:'/js/app/controller/rootoperations.html'})
                 
.when('/jobs',{controller:'ListJobsController',templateUrl:'/js/app/controller/listjobs.html'})
+                
.when('/pkgcategorycoverageimportspreadsheet',{controller:'PkgCategoryCoverageImportSpreadsheetController',templateUrl:'/js/app/controller/pkgcategorycoverageimportspreadsheet.html'})
                 
.when('/job/:guid',{controller:'ViewJobController',templateUrl:'/js/app/controller/viewjob.html'})
                 
.when('/reports',{controller:'ReportsController',templateUrl:'/js/app/controller/reports.html'})
                 
.when('/pkg/feed/builder',{controller:'PkgFeedBuilderController',templateUrl:'/js/app/controller/pkgfeedbuilder.html'})
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/service/breadcrumbfactoryservice.js Tue Dec 9 09:49:00 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/service/breadcrumbfactoryservice.js Fri Jan 2 10:56:12 2015 UTC
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014, Andrew Lindesay
+ * Copyright 2014-2015, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

@@ -429,6 +429,13 @@
                     return applyDefaults({
                         titleKey : 'breadcrumb.viewJob.title',
                         path : '/job/' + job.guid
+                    });
+                },
+
+                createPkgCategoryCoverageImportSpreadsheet : function() {
+                    return applyDefaults({
+ titleKey : 'breadcrumb.pkgCategoryCoverageImportSpreadsheet.title',
+                        path : '/pkgcategorycoverageimportspreadsheet'
                     });
                 }

=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/service/userstateservice.js Sun Jul 27 10:55:51 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/service/userstateservice.js Fri Jan 2 10:56:12 2015 UTC
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013-2014, Andrew Lindesay
+ * Copyright 2013-2015, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

@@ -15,11 +15,11 @@
     [
         '$log','$q','$rootScope','$timeout','$window',
         'jsonRpc','pkgScreenshot','errorHandling',
-        'constants','referenceData',
+        'constants','referenceData','jobs',
         function(
             $log,$q,$rootScope,$timeout,$window,
             jsonRpc,pkgScreenshot,errorHandling,
-            constants,referenceData) {
+            constants,referenceData,jobs) {

             var SIZE_CHECKED_PERMISSION_CACHE = 25;
             var SAMPLESIZE_TIMESTAMPS_OF_LAST_TOKEN_RENEWALS = 10;
@@ -61,6 +61,7 @@

jsonRpc.setHeader('Authorization', authenticationContent); pkgScreenshot.setHeader('Authorization', authenticationContent); + jobs.setHeader('Authorization', authenticationContent);
                     }
                     else {
$log.info('cannot set the token because the user state is not compatible with the token');

==============================================================================
Revision: ddd7d05babcd
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Sat Jan  3 09:08:22 2015 UTC
Log:      german translations for pkg category import process

https://code.google.com/p/haiku-depot-web-app/source/detail?r=ddd7d05babcd

Modified:
 /haikudepotserver-webapp/src/main/resources/messages_de.properties

=======================================
--- /haikudepotserver-webapp/src/main/resources/messages_de.properties Wed Dec 31 10:32:57 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/messages_de.properties Sat Jan 3 09:08:22 2015 UTC
@@ -52,6 +52,7 @@
 breadcrumb.reports.title=Berichte
 breadcrumb.listJobs.title=Aufträge auflisten
 breadcrumb.viewJob.title=Auftrag
+breadcrumb.pkgCategoryCoverageImportSpreadsheet.title=Paket-Kategorien importieren

 about.info.title=Information
 about.info.version=Version {0}
@@ -403,6 +404,10 @@
   die entsprechende URL erzeugen, die dann mit einer Feed-Sammel-Software \
   genutzt werden kann.

+pkgCategoryCoverageImportSpreadsheet.importDataFile.title=Datei
+pkgCategoryCoverageImportSpreadsheet.importDataFile.required=Für den Import wird eine Datei benötigt.
+pkgCategoryCoverageImportSpreadsheet.action.title=Importieren
+
reporting.pkgcategorycoverageexportspreadsheet.title=Verteilung der Pakete nach Kategorien reporting.pkgprominenceanduserratingspreadsheetreport.title=Empfehlungsstufen und Bewertungen der Pakete
 reporting.pkgiconspreadsheetreport.title=Icons der Pakete

==============================================================================
Revision: a02e6acf3bf4
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Sat Jan  3 09:24:53 2015 UTC
Log:      localization correction

https://code.google.com/p/haiku-depot-web-app/source/detail?r=a02e6acf3bf4

Modified:
 /haikudepotserver-webapp/src/main/resources/messages.properties
 /haikudepotserver-webapp/src/main/resources/messages_de.properties

=======================================
--- /haikudepotserver-webapp/src/main/resources/messages.properties Fri Jan 2 10:56:12 2015 UTC +++ /haikudepotserver-webapp/src/main/resources/messages.properties Sat Jan 3 09:24:53 2015 UTC
@@ -431,6 +431,7 @@
 job.jobtype.pkgrepositoryimport.title=Package Repository Import
job.jobtype.pkgprominenceanduserratingspreadsheet.title=Package Prominence and User Ratings Spreadsheet job.jobtype.pkgcategorycoverageexportspreadsheet.title=Package Category Coverage Spreadsheet +job.jobtype.pkgcategorycoverageimportspreadsheet.title=Package Category Coverage Spreadsheet Import
 job.jobtype.pkgiconspreadsheet.title=Package Icons Spreadsheet
 job.jobtype.userratingspreadsheet.title=User Rating Spreadsheet

=======================================
--- /haikudepotserver-webapp/src/main/resources/messages_de.properties Sat Jan 3 09:08:22 2015 UTC +++ /haikudepotserver-webapp/src/main/resources/messages_de.properties Sat Jan 3 09:24:53 2015 UTC
@@ -418,6 +418,7 @@
 job.jobtype.pkgrepositoryimport.title=Depots-Import
job.jobtype.pkgprominenceanduserratingspreadsheet.title=Tabelle der Empfehlungsstufen und Paket-Bewertungen job.jobtype.pkgcategorycoverageexportspreadsheet.title=Tabelle der Verteilung der Pakete nach Kategorien +job.jobtype.pkgcategorycoverageimportspreadsheet.title=Paket-Kategorien importieren
 job.jobtype.pkgiconspreadsheet.title=Tabelle der Paket-Icons
 job.jobtype.userratingspreadsheet.title=Tabelle aller Bewertungen


==============================================================================
Revision: 9aec7bcde339
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Sat Jan  3 09:29:51 2015 UTC
Log:      version 1.0.14

https://code.google.com/p/haiku-depot-web-app/source/detail?r=9aec7bcde339

Modified:
 /haikudepotserver-api1/pom.xml
 /haikudepotserver-docs/pom.xml
 /haikudepotserver-packagefile/pom.xml
 /haikudepotserver-parent/pom.xml
 /haikudepotserver-rpm-common/pom.xml
 /haikudepotserver-rpm-parent/pom.xml
 /haikudepotserver-rpm-production/pom.xml
 /haikudepotserver-rpm-test/pom.xml
 /haikudepotserver-webapp/pom.xml
 /pom.xml

=======================================
--- /haikudepotserver-api1/pom.xml      Sun Dec 21 09:33:53 2014 UTC
+++ /haikudepotserver-api1/pom.xml      Sat Jan  3 09:29:51 2015 UTC
@@ -5,7 +5,7 @@
         <artifactId>haikudepotserver-parent</artifactId>
         <groupId>org.haikuos</groupId>
         <relativePath>../haikudepotserver-parent</relativePath>
-        <version>1.0.14-SNAPSHOT</version>
+        <version>1.0.14</version>
     </parent>

     <modelVersion>4.0.0</modelVersion>
=======================================
--- /haikudepotserver-docs/pom.xml      Sun Dec 21 09:33:53 2014 UTC
+++ /haikudepotserver-docs/pom.xml      Sat Jan  3 09:29:51 2015 UTC
@@ -5,7 +5,7 @@
         <artifactId>haikudepotserver-parent</artifactId>
         <groupId>org.haikuos</groupId>
         <relativePath>../haikudepotserver-parent</relativePath>
-        <version>1.0.14-SNAPSHOT</version>
+        <version>1.0.14</version>
     </parent>

     <modelVersion>4.0.0</modelVersion>
=======================================
--- /haikudepotserver-packagefile/pom.xml       Sun Dec 21 09:33:53 2014 UTC
+++ /haikudepotserver-packagefile/pom.xml       Sat Jan  3 09:29:51 2015 UTC
@@ -5,7 +5,7 @@
         <artifactId>haikudepotserver-parent</artifactId>
         <groupId>org.haikuos</groupId>
         <relativePath>../haikudepotserver-parent</relativePath>
-        <version>1.0.14-SNAPSHOT</version>
+        <version>1.0.14</version>
     </parent>

     <modelVersion>4.0.0</modelVersion>
=======================================
--- /haikudepotserver-parent/pom.xml    Sun Dec 21 09:33:53 2014 UTC
+++ /haikudepotserver-parent/pom.xml    Sat Jan  3 09:29:51 2015 UTC
@@ -5,7 +5,7 @@
     <groupId>org.haikuos</groupId>
     <artifactId>haikudepotserver-parent</artifactId>
     <packaging>pom</packaging>
-    <version>1.0.14-SNAPSHOT</version>
+    <version>1.0.14</version>

     <licenses>
         <license>
=======================================
--- /haikudepotserver-rpm-common/pom.xml        Sun Dec 21 09:33:53 2014 UTC
+++ /haikudepotserver-rpm-common/pom.xml        Sat Jan  3 09:29:51 2015 UTC
@@ -5,7 +5,7 @@
         <artifactId>haikudepotserver-parent</artifactId>
         <groupId>org.haikuos</groupId>
         <relativePath>../haikudepotserver-parent</relativePath>
-        <version>1.0.14-SNAPSHOT</version>
+        <version>1.0.14</version>
     </parent>

     <modelVersion>4.0.0</modelVersion>
=======================================
--- /haikudepotserver-rpm-parent/pom.xml        Sun Dec 21 09:33:53 2014 UTC
+++ /haikudepotserver-rpm-parent/pom.xml        Sat Jan  3 09:29:51 2015 UTC
@@ -5,7 +5,7 @@
         <artifactId>haikudepotserver-parent</artifactId>
         <groupId>org.haikuos</groupId>
         <relativePath>../haikudepotserver-parent</relativePath>
-        <version>1.0.14-SNAPSHOT</version>
+        <version>1.0.14</version>
     </parent>

     <modelVersion>4.0.0</modelVersion>
=======================================
--- /haikudepotserver-rpm-production/pom.xml    Sun Dec 21 09:33:53 2014 UTC
+++ /haikudepotserver-rpm-production/pom.xml    Sat Jan  3 09:29:51 2015 UTC
@@ -5,7 +5,7 @@
         <artifactId>haikudepotserver-rpm-parent</artifactId>
         <groupId>org.haikuos</groupId>
         <relativePath>../haikudepotserver-rpm-parent</relativePath>
-        <version>1.0.14-SNAPSHOT</version>
+        <version>1.0.14</version>
     </parent>

     <modelVersion>4.0.0</modelVersion>
=======================================
--- /haikudepotserver-rpm-test/pom.xml  Sun Dec 21 09:33:53 2014 UTC
+++ /haikudepotserver-rpm-test/pom.xml  Sat Jan  3 09:29:51 2015 UTC
@@ -5,7 +5,7 @@
         <artifactId>haikudepotserver-rpm-parent</artifactId>
         <groupId>org.haikuos</groupId>
         <relativePath>../haikudepotserver-rpm-parent</relativePath>
-        <version>1.0.14-SNAPSHOT</version>
+        <version>1.0.14</version>
     </parent>

     <modelVersion>4.0.0</modelVersion>
=======================================
--- /haikudepotserver-webapp/pom.xml    Sun Dec 21 09:33:53 2014 UTC
+++ /haikudepotserver-webapp/pom.xml    Sat Jan  3 09:29:51 2015 UTC
@@ -5,7 +5,7 @@
         <artifactId>haikudepotserver-parent</artifactId>
         <groupId>org.haikuos</groupId>
         <relativePath>../haikudepotserver-parent</relativePath>
-        <version>1.0.14-SNAPSHOT</version>
+        <version>1.0.14</version>
     </parent>

     <modelVersion>4.0.0</modelVersion>
=======================================
--- /pom.xml    Sun Dec 21 09:33:53 2014 UTC
+++ /pom.xml    Sat Jan  3 09:29:51 2015 UTC
@@ -5,7 +5,7 @@
     <groupId>org.haikuos</groupId>
     <artifactId>haikudepotserver</artifactId>
     <packaging>pom</packaging>
-    <version>1.0.14-SNAPSHOT</version>
+    <version>1.0.14</version>

     <modules>
         <module>haikudepotserver-api1</module>

==============================================================================
Revision: c9115c719b9d
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Sat Jan  3 09:29:51 2015 UTC
Log:      version 1.0.15-SNAPSHOT

https://code.google.com/p/haiku-depot-web-app/source/detail?r=c9115c719b9d

Modified:
 /haikudepotserver-api1/pom.xml
 /haikudepotserver-docs/pom.xml
 /haikudepotserver-packagefile/pom.xml
 /haikudepotserver-parent/pom.xml
 /haikudepotserver-rpm-common/pom.xml
 /haikudepotserver-rpm-parent/pom.xml
 /haikudepotserver-rpm-production/pom.xml
 /haikudepotserver-rpm-test/pom.xml
 /haikudepotserver-webapp/pom.xml
 /pom.xml

=======================================
--- /haikudepotserver-api1/pom.xml      Sat Jan  3 09:29:51 2015 UTC
+++ /haikudepotserver-api1/pom.xml      Sat Jan  3 09:29:51 2015 UTC
@@ -5,7 +5,7 @@
         <artifactId>haikudepotserver-parent</artifactId>
         <groupId>org.haikuos</groupId>
         <relativePath>../haikudepotserver-parent</relativePath>
-        <version>1.0.14</version>
+        <version>1.0.15-SNAPSHOT</version>
     </parent>

     <modelVersion>4.0.0</modelVersion>
=======================================
--- /haikudepotserver-docs/pom.xml      Sat Jan  3 09:29:51 2015 UTC
+++ /haikudepotserver-docs/pom.xml      Sat Jan  3 09:29:51 2015 UTC
@@ -5,7 +5,7 @@
         <artifactId>haikudepotserver-parent</artifactId>
         <groupId>org.haikuos</groupId>
         <relativePath>../haikudepotserver-parent</relativePath>
-        <version>1.0.14</version>
+        <version>1.0.15-SNAPSHOT</version>
     </parent>

     <modelVersion>4.0.0</modelVersion>
=======================================
--- /haikudepotserver-packagefile/pom.xml       Sat Jan  3 09:29:51 2015 UTC
+++ /haikudepotserver-packagefile/pom.xml       Sat Jan  3 09:29:51 2015 UTC
@@ -5,7 +5,7 @@
         <artifactId>haikudepotserver-parent</artifactId>
         <groupId>org.haikuos</groupId>
         <relativePath>../haikudepotserver-parent</relativePath>
-        <version>1.0.14</version>
+        <version>1.0.15-SNAPSHOT</version>
     </parent>

     <modelVersion>4.0.0</modelVersion>
=======================================
--- /haikudepotserver-parent/pom.xml    Sat Jan  3 09:29:51 2015 UTC
+++ /haikudepotserver-parent/pom.xml    Sat Jan  3 09:29:51 2015 UTC
@@ -5,7 +5,7 @@
     <groupId>org.haikuos</groupId>
     <artifactId>haikudepotserver-parent</artifactId>
     <packaging>pom</packaging>
-    <version>1.0.14</version>
+    <version>1.0.15-SNAPSHOT</version>

     <licenses>
         <license>
=======================================
--- /haikudepotserver-rpm-common/pom.xml        Sat Jan  3 09:29:51 2015 UTC
+++ /haikudepotserver-rpm-common/pom.xml        Sat Jan  3 09:29:51 2015 UTC
@@ -5,7 +5,7 @@
         <artifactId>haikudepotserver-parent</artifactId>
         <groupId>org.haikuos</groupId>
         <relativePath>../haikudepotserver-parent</relativePath>
-        <version>1.0.14</version>
+        <version>1.0.15-SNAPSHOT</version>
     </parent>

     <modelVersion>4.0.0</modelVersion>
=======================================
--- /haikudepotserver-rpm-parent/pom.xml        Sat Jan  3 09:29:51 2015 UTC
+++ /haikudepotserver-rpm-parent/pom.xml        Sat Jan  3 09:29:51 2015 UTC
@@ -5,7 +5,7 @@
         <artifactId>haikudepotserver-parent</artifactId>
         <groupId>org.haikuos</groupId>
         <relativePath>../haikudepotserver-parent</relativePath>
-        <version>1.0.14</version>
+        <version>1.0.15-SNAPSHOT</version>
     </parent>

     <modelVersion>4.0.0</modelVersion>
=======================================
--- /haikudepotserver-rpm-production/pom.xml    Sat Jan  3 09:29:51 2015 UTC
+++ /haikudepotserver-rpm-production/pom.xml    Sat Jan  3 09:29:51 2015 UTC
@@ -5,7 +5,7 @@
         <artifactId>haikudepotserver-rpm-parent</artifactId>
         <groupId>org.haikuos</groupId>
         <relativePath>../haikudepotserver-rpm-parent</relativePath>
-        <version>1.0.14</version>
+        <version>1.0.15-SNAPSHOT</version>
     </parent>

     <modelVersion>4.0.0</modelVersion>
=======================================
--- /haikudepotserver-rpm-test/pom.xml  Sat Jan  3 09:29:51 2015 UTC
+++ /haikudepotserver-rpm-test/pom.xml  Sat Jan  3 09:29:51 2015 UTC
@@ -5,7 +5,7 @@
         <artifactId>haikudepotserver-rpm-parent</artifactId>
         <groupId>org.haikuos</groupId>
         <relativePath>../haikudepotserver-rpm-parent</relativePath>
-        <version>1.0.14</version>
+        <version>1.0.15-SNAPSHOT</version>
     </parent>

     <modelVersion>4.0.0</modelVersion>
=======================================
--- /haikudepotserver-webapp/pom.xml    Sat Jan  3 09:29:51 2015 UTC
+++ /haikudepotserver-webapp/pom.xml    Sat Jan  3 09:29:51 2015 UTC
@@ -5,7 +5,7 @@
         <artifactId>haikudepotserver-parent</artifactId>
         <groupId>org.haikuos</groupId>
         <relativePath>../haikudepotserver-parent</relativePath>
-        <version>1.0.14</version>
+        <version>1.0.15-SNAPSHOT</version>
     </parent>

     <modelVersion>4.0.0</modelVersion>
=======================================
--- /pom.xml    Sat Jan  3 09:29:51 2015 UTC
+++ /pom.xml    Sat Jan  3 09:29:51 2015 UTC
@@ -5,7 +5,7 @@
     <groupId>org.haikuos</groupId>
     <artifactId>haikudepotserver</artifactId>
     <packaging>pom</packaging>
-    <version>1.0.14</version>
+    <version>1.0.15-SNAPSHOT</version>

     <modules>
         <module>haikudepotserver-api1</module>

Other related posts:

  • » [haiku-depot-web] [haiku-depot-web-app] 6 new revisions pushed by haiku.li...@xxxxxxxxx on 2015-01-03 09:31 GMT - haiku-depot-web-app