[haiku-depot-web] [haiku-depot-web-app] 5 new revisions pushed by haiku.li...@xxxxxxxxx on 2014-02-02 09:38 GMT

  • From: haiku-depot-web-app@xxxxxxxxxxxxxx
  • To: haiku-depot-web@xxxxxxxxxxxxx
  • Date: Sun, 02 Feb 2014 09:39:21 +0000

master moved from e76c7686a48f to 43ab6c1b9f0e

5 new revisions:

Revision: 215a3491cd72
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Fri Jan 31 10:26:48 2014 UTC
Log:      + improved error handling...
http://code.google.com/p/haiku-depot-web-app/source/detail?r=215a3491cd72

Revision: 2a77219acd7d
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Fri Jan 31 10:33:41 2014 UTC
Log: + improved handling for situation where an unknown page is encountered
http://code.google.com/p/haiku-depot-web-app/source/detail?r=2a77219acd7d

Revision: 6bddac945699
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Sat Feb  1 07:54:02 2014 UTC
Log:      + top banner has better action handling with all textual options
http://code.google.com/p/haiku-depot-web-app/source/detail?r=6bddac945699

Revision: 01c6781a12ee
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Sun Feb  2 09:15:35 2014 UTC
Log:      + list repositories functionality
http://code.google.com/p/haiku-depot-web-app/source/detail?r=01c6781a12ee

Revision: 43ab6c1b9f0e
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Sun Feb  2 09:38:27 2014 UTC
Log:      + explain timestamps in api docs...
http://code.google.com/p/haiku-depot-web-app/source/detail?r=43ab6c1b9f0e

==============================================================================
Revision: 215a3491cd72
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Fri Jan 31 10:26:48 2014 UTC
Log:      + improved error handling
+ runtime information page with exception testing functions
+ better download + install of AngularJS material
+ upgrade JS materials' versions

http://code.google.com/p/haiku-depot-web-app/source/detail?r=215a3491cd72

Added:
/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetRuntimeInformationRequest.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetRuntimeInformationResult.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/RaiseExceptionRequest.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/RaiseExceptionResult.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/support/RuntimeInformationService.java
 /haikudepotserver-webapp/src/main/resources/build.properties
 /haikudepotserver-webapp/src/main/webapp/js/app/controller/more.html
/haikudepotserver-webapp/src/main/webapp/js/app/controller/morecontroller.js /haikudepotserver-webapp/src/main/webapp/js/app/controller/runtimeinformation.html /haikudepotserver-webapp/src/main/webapp/js/app/controller/runtimeinformationcontroller.js
 /haikudepotserver-webapp/src/main/webapp/js/app/filter/timestampfilter.js
/haikudepotserver-webapp/src/main/webapp/js/app/service/errorhandlingservice.js
Modified:
/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/MiscellaneousApi.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/GetUserResult.java
 /haikudepotserver-webapp/pom.xml
 /haikudepotserver-webapp/src/etc/ant/fetchwebresources-build.xml
/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/MiscellaneousApiImpl.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/UserApiImpl.java
 /haikudepotserver-webapp/src/main/resources/spring/application-context.xml
 /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml
 /haikudepotserver-webapp/src/main/webapp/css/banner.css
 /haikudepotserver-webapp/src/main/webapp/js/app/constants.js
/haikudepotserver-webapp/src/main/webapp/js/app/controller/authenticateusercontroller.js /haikudepotserver-webapp/src/main/webapp/js/app/controller/changepasswordcontroller.js /haikudepotserver-webapp/src/main/webapp/js/app/controller/createusercontroller.js /haikudepotserver-webapp/src/main/webapp/js/app/controller/editpkgiconcontroller.js /haikudepotserver-webapp/src/main/webapp/js/app/controller/homecontroller.js /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkgcontroller.js /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewusercontroller.js
 /haikudepotserver-webapp/src/main/webapp/js/app/directive/banner.html
/haikudepotserver-webapp/src/main/webapp/js/app/directive/bannerdirective.js
 /haikudepotserver-webapp/src/main/webapp/js/app/routes.js
/haikudepotserver-webapp/src/main/webapp/js/app/service/breadcrumbsservice.js
 /haikudepotserver-webapp/src/main/webapp/js/app/service/userstateservice.js

=======================================
--- /dev/null
+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetRuntimeInformationRequest.java Fri Jan 31 10:26:48 2014 UTC
@@ -0,0 +1,4 @@
+package org.haikuos.haikudepotserver.api1.model.miscellaneous;
+
+public class GetRuntimeInformationRequest {
+}
=======================================
--- /dev/null
+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetRuntimeInformationResult.java Fri Jan 31 10:26:48 2014 UTC
@@ -0,0 +1,11 @@
+package org.haikuos.haikudepotserver.api1.model.miscellaneous;
+
+public class GetRuntimeInformationResult {
+
+    public String projectVersion;
+
+    public String javaVersion;
+
+    public Long startTimestamp;
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/RaiseExceptionRequest.java Fri Jan 31 10:26:48 2014 UTC
@@ -0,0 +1,9 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.miscellaneous;
+
+public class RaiseExceptionRequest {
+}
=======================================
--- /dev/null
+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/RaiseExceptionResult.java Fri Jan 31 10:26:48 2014 UTC
@@ -0,0 +1,9 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.miscellaneous;
+
+public class RaiseExceptionResult {
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/support/RuntimeInformationService.java Fri Jan 31 10:26:48 2014 UTC
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.support;
+
+import com.google.common.base.Strings;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+/**
+ * <p>This service is able to provide information about the current runtime.</p>
+ */
+
+public class RuntimeInformationService {
+
+    private long startTimestamp = System.currentTimeMillis();
+
+    private Properties buildProperties = null;
+
+    private Properties getBuildProperties() {
+        if(null==buildProperties) {
+            buildProperties = new Properties();
+
+            InputStream inputStream = null;
+
+            try {
+ inputStream = RuntimeInformationService.class.getResourceAsStream("/build.properties");
+
+                if(null==inputStream) {
+ throw new IllegalStateException("the build properties do not seem to be present.");
+                }
+
+                buildProperties.load(inputStream);
+            }
+            catch(IOException ioe) {
+ throw new IllegalStateException("an issue has arisen loading the build properties");
+            }
+            finally {
+                Closeables.closeQuietly(inputStream);
+            }
+        }
+
+        return buildProperties;
+    }
+
+    private String versionOrPlaceholder(String v) {
+        return Strings.isNullOrEmpty(v) ? "???" : v;
+    }
+
+    public String getProjectVersion() {
+ return versionOrPlaceholder(getBuildProperties().getProperty("project.version"));
+    }
+
+    public String getJavaVersion() {
+        return versionOrPlaceholder(System.getProperty("java.version"));
+    }
+
+    public long getStartTimestamp() {
+        return startTimestamp;
+    }
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/resources/build.properties Fri Jan 31 10:26:48 2014 UTC
@@ -0,0 +1,2 @@
+# Values in this properties file will be replaced as part of the build process.
+project.version=${project.version}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/more.html Fri Jan 31 10:26:48 2014 UTC
@@ -0,0 +1,23 @@
+<breadcrumbs items="breadcrumbItems"></breadcrumbs>
+
+<div class="content-container">
+
+    <h1>About</h1>
+    <p>
+ This is an application-server (version <code>{{serverProjectVersion}}</code>) called "Haiku Depot Server" that stores and
+        allows interaction with software packages for the
+        <a href="http://www.haiku-os.org";>Haiku Operating System</a>.
+    </p>
+
+    <h1>Actions</h1>
+    <ul>
+        <li>
+            <a href="" ng-click="goHome()">Home</a>
+        </li>
+        <li ng-show="canGoRuntimeInformation">
+ <a href="" ng-click="goRuntimeInformation()">Runtime information</a>
+        </li>
+    </ul>
+
+</div>
+<div class="footer"></div>
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/morecontroller.js Fri Jan 31 10:26:48 2014 UTC
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+angular.module('haikudepotserver').controller(
+    'MoreController',
+    [
+        '$scope','$log','$location',
+        'jsonRpc','constants','userState',
+        'breadcrumbs','errorHandling',
+        function(
+            $scope,$log,$location,
+            jsonRpc,constants,userState,
+            breadcrumbs,errorHandling) {
+
+            $scope.breadcrumbItems = [ breadcrumbs.createMore() ];
+            $scope.serverStartTimestamp = undefined;
+            $scope.serverProjectVersion = '...';
+
+            $scope.goHome = function() {
+                $location.path("/").search({});
+            }
+
+            // -------------------
+            // USER
+
+            function refreshAuthorization() {
+
+                function disallowAll() {
+                    $scope.canGoRuntimeInformation = false;
+                }
+
+                var u = userState.user();
+
+                if(!u || !u.nickname) {
+                    disallowAll();
+                }
+                else {
+                    jsonRpc.call(
+                            constants.ENDPOINT_API_V1_USER,
+                            "getUser",
+                            [{
+                                nickname : u.nickname
+                            }]
+                        ).then(
+                        function(result) {
+                            if(result.isRoot) {
+                                $scope.canGoRuntimeInformation = true;
+                            }
+                        },
+                        function(err) {
+                            errorHandling.handleJsonRpcError(err);
+                        }
+                    );
+                }
+            }
+
+            refreshAuthorization();
+
+            // -------------------
+            // RUNTIME INFORMATION
+
+            function refreshRuntimeInformation() {
+                jsonRpc.call(
+                        constants.ENDPOINT_API_V1_MISCELLANEOUS,
+                        "getRuntimeInformation",
+                        [{}]
+                    ).then(
+                    function(result) {
+ $scope.serverProjectVersion = result.projectVersion;
+                        $log.info('have fetched the runtime information');
+                    },
+                    function(err) {
+                        errorHandling.handleJsonRpcError(err);
+                    }
+                );
+            }
+
+            refreshRuntimeInformation();
+
+            $scope.canGoRuntimeInformation = false;
+
+            $scope.goRuntimeInformation = function() {
+                $location.path('/runtimeinformation').search({});
+            }
+
+        }
+    ]
+);
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/runtimeinformation.html Fri Jan 31 10:26:48 2014 UTC
@@ -0,0 +1,45 @@
+<breadcrumbs items="breadcrumbItems"></breadcrumbs>
+
+<div class="content-container">
+
+    <h1>Actions</h1>
+    <ul>
+ <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>
+    </ul>
+
+    <h1>Uptime</h1>
+    <p>
+        This instance of the application-server has been running since
+ <span ng-show="serverStartTimestamp">{{serverStartTimestamp| timestamp}}</span>
+        <span ng-show="!serverStartTimestamp">...</span>.
+    </p>
+
+    <h1>Versions</h1>
+
+    <p>The application has been running for some time.</p>
+
+    <table class="table-general">
+        <thead>
+        <th>Component</th>
+        <th>Version</th>
+        </thead>
+        <tbody>
+        <tr>
+            <td>Haiku Depot Server</td>
+            <td><code>{{versions.serverProject}}</code></td>
+        </tr>
+        <tr>
+            <td>Java (on Server)</td>
+            <td><code>{{versions.serverJava}}</code></td>
+        </tr>
+        <tr>
+            <td>AngularJS</td>
+            <td><code>{{versions.angularFull}}</code></td>
+        </tr>
+        </tbody>
+    </table>
+
+
+</div>
+<div class="footer"></div>
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/runtimeinformationcontroller.js Fri Jan 31 10:26:48 2014 UTC
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+angular.module('haikudepotserver').controller(
+    'RuntimeInformationController',
+    [
+        '$scope','$log','$location',
+        'jsonRpc','constants','userState',
+        'breadcrumbs','errorHandling',
+        function(
+            $scope,$log,$location,
+            jsonRpc,constants,userState,
+            breadcrumbs,errorHandling) {
+
+            $scope.breadcrumbItems = [
+                breadcrumbs.createMore(),
+                breadcrumbs.createRuntimeInformation()
+            ];
+
+            $scope.serverStartTimestamp = undefined;
+            $scope.versions = {
+                angularFull : angular.version.full,
+                serverProject : '...',
+                serverJava : '...'
+            };
+
+            function refreshRuntimeInformation() {
+                jsonRpc.call(
+                        constants.ENDPOINT_API_V1_MISCELLANEOUS,
+                        "getRuntimeInformation",
+                        [{}]
+                    ).then(
+                    function(result) {
+ $scope.versions.serverProject = result.projectVersion;
+                        $scope.versions.serverJava = result.javaVersion;
+ $scope.serverStartTimestamp = result.startTimestamp;
+                        $log.info('have fetched the runtime information');
+                    },
+                    function(err) {
+                        errorHandling.handleJsonRpcError(err);
+                    }
+                );
+            }
+
+            refreshRuntimeInformation();
+
+            // -------------------
+            // ERROR HANDLING TESTING
+
+            $scope.goRaiseExceptionInLocalRuntime = function() {
+                throw 'test exception in javascript environment';
+            }
+
+            $scope.goRaiseExceptionInServerRuntime = function() {
+                jsonRpc.call(
+                        constants.ENDPOINT_API_V1_MISCELLANEOUS,
+                        "raiseException",
+                        [{}]
+                    ).then(
+                    function(result) {
+ $log.error('the exception raised on the server runtime -> should not have reached this point');
+                    },
+                    function(err) {
+                        errorHandling.handleJsonRpcError(err);
+                    }
+                );
+            }
+        }
+    ]
+);
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/filter/timestampfilter.js Fri Jan 31 10:26:48 2014 UTC
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+/**
+ * <p>This filter will present a timestamp that is expressed as milliseconds since the epoc relative to UTC into a
+ * local timestamp of the browser.</p>
+ */
+
+angular.module('haikudepotserver').filter('timestamp', function() {
+        return function(input) {
+            return moment.utc(input).local().format('YYYY-MM-DD HH:mm:ss');
+        }
+    }
+);
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/service/errorhandlingservice.js Fri Jan 31 10:26:48 2014 UTC
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+/**
+ * <p>This service is used to centralize the handling of errors; providing consistent handling and error feedback.
+ */
+
+angular.module('haikudepotserver').factory('errorHandling',
+    [ '$log','$location',
+        function($log,$location) {
+
+            var ErrorHandlingService = {
+
+                /**
+ * <p>When a JSON-RPC failure occurs, this method can be invoked to provide uniform logging and
+                 * handling.</p>
+                 */
+
+                handleJsonRpcError : function(jsonRpcErrorEnvelope) {
+                    if(null==jsonRpcErrorEnvelope) {
+ $log.error('json-rpc error; cause is unknown as no error envelope was available');
+                    }
+                    else {
+ var code = jsonRpcErrorEnvelope.code ? jsonRpcErrorEnvelope.code : '?'; + var message = jsonRpcErrorEnvelope.message ? jsonRpcErrorEnvelope.message : '?'; + $log.error('json-rpc error; code:'+code+", msg:"+message);
+                    }
+
+                    $location.path("/error").search({});
+                }
+            };
+
+            return ErrorHandlingService;
+
+        }
+    ]
+);
+
+// note the use of the injector service here is used to avoid cyclic dependencies
+// with directly injecting $location.
+
+angular.module('haikudepotserver').config([
+    '$provide',
+    function($provide) {
+        $provide.decorator('$exceptionHandler', [
+            '$delegate','$injector',
+            function($delegate, $injector) {
+                return function(exception, cause) {
+                    var $location = $injector.get('$location');
+                    $delegate(exception,cause);
+                    $location.path("/error").search({});
+                }
+            }
+        ]);
+
+    }
+]);
=======================================
--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/MiscellaneousApi.java Fri Nov 15 08:51:45 2013 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/MiscellaneousApi.java Fri Jan 31 10:26:48 2014 UTC
@@ -1,19 +1,29 @@
 /*
- * Copyright 2013, Andrew Lindesay
+ * Copyright 2013-2014, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

 package org.haikuos.haikudepotserver.api1;

 import com.googlecode.jsonrpc4j.JsonRpcService;
-import org.haikuos.haikudepotserver.api1.model.miscellaneous.GetAllArchitecturesRequest; -import org.haikuos.haikudepotserver.api1.model.miscellaneous.GetAllArchitecturesResult; -import org.haikuos.haikudepotserver.api1.model.miscellaneous.GetAllMessagesRequest; -import org.haikuos.haikudepotserver.api1.model.miscellaneous.GetAllMessagesResult;
+import org.haikuos.haikudepotserver.api1.model.miscellaneous.*;

 @JsonRpcService("/api/v1/miscellaneous")
 public interface MiscellaneousApi {

+    /**
+ * <p>This method will raise a runtime exception to test the behaviour of the server and client in this
+     * situation.</p>
+     */
+
+ RaiseExceptionResult raiseException(RaiseExceptionRequest raiseExceptionRequest);
+
+    /**
+ * <p>This method will return information about the running application server.</p>
+     */
+
+ GetRuntimeInformationResult getRuntimeInformation(GetRuntimeInformationRequest getRuntimeInformationRequest);
+
     /**
* <p>This method will return all of the localization messages that might be able to be displayed
      * to the user from the result of validation problems and so on.</p>
=======================================
--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/GetUserResult.java Fri Nov 15 08:51:45 2013 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/user/GetUserResult.java Fri Jan 31 10:26:48 2014 UTC
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013, Andrew Lindesay
+ * Copyright 2013-2014, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

@@ -9,4 +9,6 @@

     public String nickname;

+    public Boolean isRoot;
+
 }
=======================================
--- /haikudepotserver-webapp/pom.xml    Sat Jan 18 09:59:17 2014 UTC
+++ /haikudepotserver-webapp/pom.xml    Fri Jan 31 10:26:48 2014 UTC
@@ -20,16 +20,16 @@
         identifier.
         -->

- <webresources.guid>79A1B854-6665-4254-B24C-E40564DCA393</webresources.guid> + <webresources.guid>D4BAB0F5-EFDC-436B-8692-D756A5CE9FCA</webresources.guid>

         <!--
These are the versions of web-resources that are downloaded as part of this version of web resources
         -->
         <angularuiutils.version>0.4.0</angularuiutils.version>
-        <angularjs.version>1.2.0</angularjs.version>
-        <base64js.version>0.1.3</base64js.version>
+        <angularjs.version>1.2.10</angularjs.version>
+        <base64js.version>0.2.0</base64js.version>
         <underscorejs.version>1.5.2</underscorejs.version>
-        <momentjs.version>2.3.1</momentjs.version>
+        <momentjs.version>2.5.1</momentjs.version>

         <!--
These are the source (base URLs) for the web resources to be downloaded from.
@@ -157,6 +157,7 @@
                 <filtering>true</filtering>
                 <includes>
                     <include>webresources.properties</include>
+                    <include>build.properties</include>
                 </includes>
             </resource>
             <resource>
@@ -164,6 +165,7 @@
                 <filtering>false</filtering>
                 <excludes>
                     <exclude>webresources.properties</exclude>
+                    <exclude>build.properties</exclude>
                 </excludes>
             </resource>
         </resources>
=======================================
--- /haikudepotserver-webapp/src/etc/ant/fetchwebresources-build.xml Mon Jan 13 10:50:43 2014 UTC +++ /haikudepotserver-webapp/src/etc/ant/fetchwebresources-build.xml Fri Jan 31 10:26:48 2014 UTC
@@ -11,12 +11,6 @@

         <mkdir dir="${js.libsroot}"/>

-        <!-- user interface / events / mvc -->
- <get src="${js.angularjsurl}/${angularjs.version}/angular.js" dest="${js.libsroot}/angular.js" skipexisting="true" /> - <get src="${js.angularjsurl}/${angularjs.version}/angular.min.js" dest="${js.libsroot}/angular-min.js" skipexisting="true" /> - <get src="${js.angularjsurl}/${angularjs.version}/angular-route.js" dest="${js.libsroot}/angular-route.js" skipexisting="true" /> - <get src="${js.angularjsurl}/${angularjs.version}/angular-route.min.js" dest="${js.libsroot}/angular-route-min.js" skipexisting="true" />
-
<get src="${js.cdnurl}/angular-ui/${angularuiutils.version}/angular-ui-ieshiv.js" dest="${js.libsroot}/angular-ui-ieshiv.js" skipexisting="true" /> <get src="${js.cdnurl}/angular-ui/${angularuiutils.version}/angular-ui-ieshiv.min.js" dest="${js.libsroot}/angular-ui-ieshiv-min.js" skipexisting="true" /> <!--<get src="${js.cdnurl}/angular-ui/${angularuiutils.version}/angular-ui.css" dest="${js.libsroot}/angular-ui.css" skipexisting="true" />-->
@@ -37,6 +31,22 @@

     </target>

-    <target name="main" depends="js"/>
+    <target name="angularjs.check">
+ <available property="angularjs.present" file="${js.libsroot}/angular"/>
+    </target>
+
+ <target name="angularjs" depends="angularjs.check" unless="angularjs.present"> + <get src="${js.angularjsurl}/${angularjs.version}/angular-${angularjs.version}.zip" dest="${java.io.tmpdir}/angular.zip" />
+        <unzip src="${java.io.tmpdir}/angular.zip" dest="${js.libsroot}">
+            <patternset>
+ <exclude name="angular-${angularjs.version}/docs"></exclude> + <exclude name="angular-${angularjs.version}/docs/**"></exclude>
+            </patternset>
+ <globmapper from="angular-${angularjs.version}/*" to="angular/*"/>
+        </unzip>
+        <delete file="${java.io.tmpdir}/angular.zip"/>
+    </target>
+
+    <target name="main" depends="js,angularjs"/>

 </project>
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/MiscellaneousApiImpl.java Wed Dec 11 08:25:33 2013 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/MiscellaneousApiImpl.java Fri Jan 31 10:26:48 2014 UTC
@@ -1,23 +1,26 @@
 /*
- * Copyright 2013, Andrew Lindesay
+ * Copyright 2013-2014, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

 package org.haikuos.haikudepotserver.api1;

 import com.google.common.base.Function;
+import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Predicate;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.configuration.server.ServerRuntime;
-import org.haikuos.haikudepotserver.api1.model.miscellaneous.GetAllArchitecturesRequest; -import org.haikuos.haikudepotserver.api1.model.miscellaneous.GetAllArchitecturesResult; -import org.haikuos.haikudepotserver.api1.model.miscellaneous.GetAllMessagesRequest; -import org.haikuos.haikudepotserver.api1.model.miscellaneous.GetAllMessagesResult;
+import org.haikuos.haikudepotserver.api1.model.miscellaneous.*;
 import org.haikuos.haikudepotserver.dataobjects.Architecture;
+import org.haikuos.haikudepotserver.dataobjects.User;
 import org.haikuos.haikudepotserver.support.Closeables;
+import org.haikuos.haikudepotserver.support.RuntimeInformationService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Component;

 import javax.annotation.Resource;
@@ -28,12 +31,49 @@
 import java.util.Properties;

 @Component
-public class MiscellaneousApiImpl implements MiscellaneousApi {
+public class MiscellaneousApiImpl extends AbstractApiImpl implements MiscellaneousApi {
+
+ protected static Logger logger = LoggerFactory.getLogger(PkgApiImpl.class);

     public final static String RESOURCE_MESSAGES = "/messages.properties";

     @Resource
     ServerRuntime serverRuntime;
+
+    @Resource
+    RuntimeInformationService runtimeInformationService;
+
+    @Override
+ public RaiseExceptionResult raiseException(RaiseExceptionRequest raiseExceptionRequest) {
+
+        final ObjectContext context = serverRuntime.getContext();
+ Optional<User> authUserOptional = tryObtainAuthenticatedUser(context);
+
+ if(authUserOptional.isPresent() && authUserOptional.get().getIsRoot()) {
+            throw new RuntimeException("test exception");
+        }
+
+ logger.warn("attempt to raise a test exception without being authenticated as root");
+
+        return new RaiseExceptionResult();
+    }
+
+    @Override
+ public GetRuntimeInformationResult getRuntimeInformation(GetRuntimeInformationRequest getRuntimeInformationRequest) {
+
+        final ObjectContext context = serverRuntime.getContext();
+ Optional<User> authUserOptional = tryObtainAuthenticatedUser(context);
+
+ GetRuntimeInformationResult result = new GetRuntimeInformationResult(); + result.projectVersion = runtimeInformationService.getProjectVersion();
+
+ if(authUserOptional.isPresent() && authUserOptional.get().getIsRoot()) { + result.javaVersion = runtimeInformationService.getJavaVersion(); + result.startTimestamp = runtimeInformationService.getStartTimestamp();
+        }
+
+        return result;
+    }

     @Override
public GetAllArchitecturesResult getAllArchitectures(GetAllArchitecturesRequest getAllArchitecturesRequest) {
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/UserApiImpl.java Thu Jan 16 08:37:44 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/UserApiImpl.java Fri Jan 31 10:26:48 2014 UTC
@@ -108,6 +108,7 @@

         GetUserResult result = new GetUserResult();
         result.nickname = user.get().getNickname();
+        result.isRoot = user.get().getIsRoot();
         return result;
     }

=======================================
--- /haikudepotserver-webapp/src/main/resources/spring/application-context.xml Sat Jan 18 09:59:17 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/spring/application-context.xml Fri Jan 31 10:26:48 2014 UTC
@@ -54,4 +54,8 @@

<bean class="org.haikuos.haikudepotserver.support.logging.LoggingSetupOrchestration" init-method="init"/>

+    <!-- SUNDRY -->
+
+ <bean class="org.haikuos.haikudepotserver.support.RuntimeInformationService"></bean>
+
 </beans>
=======================================
--- /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml Sat Jan 18 09:59:17 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml Fri Jan 31 10:26:48 2014 UTC
@@ -24,8 +24,8 @@
<value>/js/lib/${webresources.guid}/base64-min.js</value> <value>/js/lib/${webresources.guid}/moment-min.js</value> <value>/js/lib/${webresources.guid}/underscore-min.js</value> - <value>/js/lib/${webresources.guid}/angular-min.js</value> - <value>/js/lib/${webresources.guid}/angular-route-min.js</value> + <value>/js/lib/${webresources.guid}/angular/angular.min.js</value> + <value>/js/lib/${webresources.guid}/angular/angular-route.min.js</value> <value>/js/lib/${webresources.guid}/angular-ui.js</value> <value>/js/lib/${webresources.guid}/base64-min.js</value>
                         </list>
@@ -66,6 +66,8 @@
<value>/js/app/controller/authenticateusercontroller.js</value> <value>/js/app/controller/editpkgiconcontroller.js</value> <value>/js/app/controller/changepasswordcontroller.js</value> + <value>/js/app/controller/morecontroller.js</value> + <value>/js/app/controller/runtimeinformationcontroller.js</value>

<value>/js/app/service/jsonrpcservice.js</value> <value>/js/app/service/messagesourceservice.js</value>
@@ -73,6 +75,9 @@
<value>/js/app/service/referencedataservice.js</value> <value>/js/app/service/pkgiconservice.js</value> <value>/js/app/service/breadcrumbsservice.js</value> + <value>/js/app/service/errorhandlingservice.js</value>
+
+ <value>/js/app/filter/timestampfilter.js</value>

                         </list>
                     </property>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/css/banner.css Tue Jan 14 09:30:30 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/css/banner.css Fri Jan 31 10:26:48 2014 UTC
@@ -32,7 +32,7 @@
     vertical-align: middle;
 }

-#banner-user {
+#banner-actions {
     position: absolute;
     top: 12px;
     right: 0px;
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/constants.js Fri Nov 15 08:51:45 2013 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/constants.js Fri Jan 31 10:26:48 2014 UTC
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013, Andrew Lindesay
+ * Copyright 2013-2014, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

@@ -10,19 +10,7 @@
         ENDPOINT_API_V1_PKG : '/api/v1/pkg',
         ENDPOINT_API_V1_CAPTCHA : '/api/v1/captcha',
         ENDPOINT_API_V1_MISCELLANEOUS : '/api/v1/miscellaneous',
-        ENDPOINT_API_V1_USER : '/api/v1/user',
-
-        /**
- * <p>This function expects to be supplied a JSON-RPC error object and will then direct the user to - * an error page from where they can return into the application again.</p>
-         */
-
-        ERRORHANDLING_JSONRPC : function(err,$location,$log) {
-            if($log) {
- $log.error('an error has arisen in invoking a json-rpc method');
-            }
+        ENDPOINT_API_V1_USER : '/api/v1/user'

-            $location.path("/error").search({});
-        }
     }
 );
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/authenticateusercontroller.js Thu Jan 16 08:37:44 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/authenticateusercontroller.js Fri Jan 31 10:26:48 2014 UTC
@@ -7,10 +7,10 @@
     'AuthenticateUserController',
     [
         '$scope','$log','$location',
-        'jsonRpc','constants','userState',
+        'jsonRpc','constants','userState','errorHandling',
         function(
             $scope,$log,$location,
-            jsonRpc,constants,userState) {
+            jsonRpc,constants,userState,errorHandling) {

             if(userState.user()) {
throw 'it is not possible to enter the authenticate user controller with a currently authenticated user';
@@ -95,7 +95,7 @@
                     },
                     function(err) {
                         $scope.amAuthenticating = false;
- constants.ERRORHANDLING_JSONRPC(err,$location,$log);
+                        errorHandling.handleJsonRpcError(err);
                     }
                 );

=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/changepasswordcontroller.js Thu Jan 16 08:37:44 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/changepasswordcontroller.js Fri Jan 31 10:26:48 2014 UTC
@@ -7,10 +7,10 @@
     'ChangePasswordController',
     [
         '$scope','$log','$location','$routeParams',
-        'jsonRpc','constants','breadcrumbs','userState',
+        'jsonRpc','constants','breadcrumbs','userState','errorHandling',
         function(
             $scope,$log,$location,$routeParams,
-            jsonRpc,constants,breadcrumbs,userState) {
+            jsonRpc,constants,breadcrumbs,userState,errorHandling) {

             $scope.breadcrumbItems = undefined;
             $scope.user = undefined;
@@ -55,7 +55,7 @@
                         $log.info('fetched user; '+result.nickname);
                     },
                     function(err) {
- constants.ERRORHANDLING_JSONRPC(err,$location,$log);
+                        errorHandling.handleJsonRpcError(err);
                     }
                 );
             };
@@ -77,7 +77,7 @@
                         refreshBreadcrumbItems();
                     },
                     function(err) {
- constants.ERRORHANDLING_JSONRPC(err,$location,$log);
+                        errorHandling.handleJsonRpcError(err);
                     }
                 );
             }
@@ -153,7 +153,7 @@
                                         }
                                         else {
$log.error('other validation failures exist; will invoke default handling'); - constants.ERRORHANDLING_JSONRPC(err,$location,$log); + errorHandling.handleJsonRpcError(err);
                                         }
                                     })
                                 }
@@ -166,7 +166,7 @@
                                 break;

                             default:
- constants.ERRORHANDLING_JSONRPC(err,$location,$log);
+                                errorHandling.handleJsonRpcError(err);
                                 break;
                         }
                     }
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/createusercontroller.js Thu Jan 16 08:37:44 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/createusercontroller.js Fri Jan 31 10:26:48 2014 UTC
@@ -7,10 +7,10 @@
     'CreateUserController',
     [
         '$scope','$log','$location',
-        'jsonRpc','constants',
+        'jsonRpc','constants','errorHandling',
         function(
             $scope,$log,$location,
-            jsonRpc,constants) {
+            jsonRpc,constants,errorHandling) {

             $scope.breadcrumbItems = undefined;
             $scope.captchaToken = undefined;
@@ -57,7 +57,7 @@
                         refreshBreadcrumbItems();
                     },
                     function(err) {
- constants.ERRORHANDLING_JSONRPC(err,$location,$log);
+                        errorHandling.handleJsonRpcError(err);
                     }
                 );
             }
@@ -132,7 +132,7 @@
                                         }
                                         else {
$log.error('other validation failures exist; will invoke default handling'); - constants.ERRORHANDLING_JSONRPC(err,$location,$log); + errorHandling.handleJsonRpcError(err);
                                         }
                                     })
                                 }
@@ -145,7 +145,7 @@
                                 break;

                             default:
- constants.ERRORHANDLING_JSONRPC(err,$location,$log);
+                                errorHandling.handleJsonRpcError(err);
                                 break;
                         }
                     }
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/editpkgiconcontroller.js Tue Jan 14 09:30:30 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/editpkgiconcontroller.js Fri Jan 31 10:26:48 2014 UTC
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013, Andrew Lindesay
+ * Copyright 2013-2014, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

@@ -7,10 +7,10 @@
     'EditPkgIconController',
     [
         '$scope','$log','$location','$routeParams',
-        'jsonRpc','constants','pkgIcon',
+        'jsonRpc','constants','pkgIcon','errorHandling',
         function(
             $scope,$log,$location,$routeParams,
-            jsonRpc,constants,pkgIcon) {
+            jsonRpc,constants,pkgIcon,errorHandling) {

             $scope.breadcrumbItems = undefined;
             $scope.pkg = undefined;
@@ -47,7 +47,7 @@
                         refreshBreadcrumbItems();
                     },
                     function(err) {
- constants.ERRORHANDLING_JSONRPC(err,$location,$log);
+                        errorHandling.handleJsonRpcError(err);
                     }
                 );
             }
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/homecontroller.js Tue Jan 14 09:30:30 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/homecontroller.js Fri Jan 31 10:26:48 2014 UTC
@@ -1,14 +1,16 @@
 /*
- * Copyright 2013, Andrew Lindesay
+ * Copyright 2013-2014, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

 angular.module('haikudepotserver').controller(
     'HomeController',
     [
-        
'$scope','$q','$log','$location','jsonRpc','constants','userState','architectures','messageSource',
+        '$scope','$q','$log','$location',
+        
'jsonRpc','constants','userState','architectures','messageSource','errorHandling',
         function(
- $scope,$q,$log,$location,jsonRpc,constants,userState,architectures,messageSource) {
+            $scope,$q,$log,$location,
+ jsonRpc,constants,userState,architectures,messageSource,errorHandling) {

             const PAGESIZE = 14;

@@ -193,7 +195,7 @@
                         amFetchingPkgs = false;
                     },
                     function(err) {
- constants.ERRORHANDLING_JSONRPC(err,$location,$log);
+                        errorHandling.handleJsonRpcError(err);
                     }
                 );

=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkgcontroller.js Tue Jan 14 09:30:30 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkgcontroller.js Fri Jan 31 10:26:48 2014 UTC
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013, Andrew Lindesay
+ * Copyright 2013-2014, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

@@ -7,10 +7,10 @@
     'ViewPkgController',
     [
         '$scope','$log','$location','$routeParams',
-        'jsonRpc','constants','userState',
+        'jsonRpc','constants','userState','errorHandling',
         function(
             $scope,$log,$location,$routeParams,
-            jsonRpc,constants,userState) {
+            jsonRpc,constants,userState,errorHandling) {

             $scope.breadcrumbItems = undefined;
             $scope.pkg = undefined;
@@ -69,7 +69,7 @@
                         refreshBreadcrumbItems();
                     },
                     function(err) {
- constants.ERRORHANDLING_JSONRPC(err,$location,$log);
+                        errorHandling.handleJsonRpcError(err);
                     }
                 );
             }
@@ -91,7 +91,7 @@
                     },
                     function(err) {
$log.error('unable to remove the icons for '+$routeParams.name+' pkg'); - constants.ERRORHANDLING_JSONRPC(err,$location,$log);
+                        errorHandling.handleJsonRpcError(err);
                     }
                 );

=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewusercontroller.js Thu Jan 16 08:37:44 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewusercontroller.js Fri Jan 31 10:26:48 2014 UTC
@@ -7,10 +7,10 @@
     'ViewUserController',
     [
         '$scope','$log','$location','$routeParams',
-        'jsonRpc','constants',
+        'jsonRpc','constants','errorHandling',
         function(
             $scope,$log,$location,$routeParams,
-            jsonRpc,constants) {
+            jsonRpc,constants,errorHandling) {

             $scope.breadcrumbItems = undefined;
             $scope.user = undefined;
@@ -42,7 +42,7 @@
                         $log.info('fetched user; '+result.nickname);
                     },
                     function(err) {
- constants.ERRORHANDLING_JSONRPC(err,$location,$log);
+                        errorHandling.handleJsonRpcError(err);
                     }
                 );
             };
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/directive/banner.html Tue Jan 14 09:30:30 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/banner.html Fri Jan 31 10:26:48 2014 UTC
@@ -2,7 +2,10 @@
     <span id="banner-title" ng-click="goHome()">
         <div>Haiku Depot Server</div>
     </span>
-    <span id="banner-user">
+    <span id="banner-actions">
+        <span ng-show="canGoMore()">
+            <a href="" ng-click="goMore()">more</a> |
+        </span>
         <span ng-show="canAuthenticateOrCreate()">
             <a href="" ng-click="goAuthenticateUser()">login</a>
             |
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/directive/bannerdirective.js Mon Jan 13 10:50:43 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/bannerdirective.js Fri Jan 31 10:26:48 2014 UTC
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013, Andrew Lindesay
+ * Copyright 2013-2014, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

@@ -21,6 +21,11 @@
                     $rootScope,$scope,$log,$location,$route,
                     userState) {

+                    $scope.goMore = function() {
+                        $location.path('/more').search({});
+                        return false;
+                    }
+
                     // This will take the user back to the home page.

                     $scope.goHome = function() {
@@ -35,6 +40,11 @@
                         var p = $location.path();
return '/error' == p || '/authenticateuser' == p | | '/createuser' == p;
                     }
+
+                    $scope.canGoMore = function() {
+                        var p = $location.path();
+                        return '/error' != p && '/more' != p;
+                    }

                     $scope.canAuthenticateOrCreate = function() {
return !isLocationPathDisablingUserState() && !userState.user();
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/routes.js Thu Jan 16 08:37:44 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/routes.js Fri Jan 31 10:26:48 2014 UTC
@@ -8,6 +8,8 @@
         '$routeProvider',
         function($routeProvider) {
             $routeProvider
+ .when('/runtimeinformation',{controller:'RuntimeInformationController', templateUrl:'/js/app/controller/runtimeinformation.html'}) + .when('/more',{controller:'MoreController', templateUrl:'/js/app/controller/more.html'}) .when('/authenticateuser',{controller:'AuthenticateUserController', templateUrl:'/js/app/controller/authenticateuser.html'}) .when('/createuser',{controller:'CreateUserController', templateUrl:'/js/app/controller/createuser.html'}) .when('/changepassword/:nickname',{controller:'ChangePasswordController', templateUrl:'/js/app/controller/changepassword.html'})
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/service/breadcrumbsservice.js Thu Jan 16 08:37:44 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/service/breadcrumbsservice.js Fri Jan 31 10:26:48 2014 UTC
@@ -13,6 +13,20 @@

             var BreadcrumbsService = {

+                createRuntimeInformation : function() {
+                    return {
+                        title : 'Runtime Information',
+                        path : '/runtimeinformation'
+                    };
+                },
+
+                createMore : function() {
+                  return {
+                      title : 'More',
+                      path : '/more'
+                  };
+                },
+
                 createViewUser : function(user) {
                     return {
                         title : user.nickname,
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/service/userstateservice.js Mon Jan 13 10:50:43 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/service/userstateservice.js Fri Jan 31 10:26:48 2014 UTC
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013, Andrew Lindesay
+ * Copyright 2013-2014, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

@@ -11,8 +11,8 @@

 angular.module('haikudepotserver').factory('userState',
     [
-        '$log','$q','jsonRpc','pkgIcon','referenceData','constants',
-        function($log,$q,jsonRpc,pkgIcon,referenceData,constants) {
+        '$log','$q','jsonRpc','pkgIcon',
+        function($log,$q,jsonRpc,pkgIcon) {

             var user = undefined;

@@ -21,7 +21,7 @@
                 /**
* <p>Invoked with no argument, this function will return the user. If it is supplied with null then * it will set the current user to empty. If it is supplied with a user value, it will configure the
-                 * user.</p>
+ * user. The user should consist of the 'nickname' and the 'passwordClear'.</p>
                  */

                 user : function(value) {

==============================================================================
Revision: 2a77219acd7d
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Fri Jan 31 10:33:41 2014 UTC
Log: + improved handling for situation where an unknown page is encountered

http://code.google.com/p/haiku-depot-web-app/source/detail?r=2a77219acd7d

Modified:
 /haikudepotserver-webapp/src/main/webapp/js/app/routes.js
/haikudepotserver-webapp/src/main/webapp/js/app/service/errorhandlingservice.js

=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/routes.js Fri Jan 31 10:26:48 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/routes.js Fri Jan 31 10:33:41 2014 UTC
@@ -28,7 +28,7 @@
                         ]
                     }
                 })
-                .otherwise({redirectTo:'/'});
+ .otherwise({controller:'OtherwiseController', template: '<div></div>'});
         }
     ]
 );
@@ -41,3 +41,22 @@
 angular.module('haikudepotserver').run(function($http,$templateCache) {
     $http.get('/js/app/controller/error.html', { cache: $templateCache });
 });
+
+/*
+This is a controller for an unknown page which just goes through to the error page.
+ */
+
+angular.module('haikudepotserver').controller(
+    'OtherwiseController',
+    [
+        '$log','$location',
+        'errorHandling',
+        function(
+            $log,$location,
+            errorHandling) {
+
+            errorHandling.handleUnknownLocation();
+
+        }
+    ]
+);
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/service/errorhandlingservice.js Fri Jan 31 10:26:48 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/service/errorhandlingservice.js Fri Jan 31 10:33:41 2014 UTC
@@ -29,7 +29,17 @@
                     }

                     $location.path("/error").search({});
+                },
+
+                /**
+ * <p>This situation arises when somebody navigates to a page that does not exist.</p>
+                 */
+
+                handleUnknownLocation : function() {
+                    $log.error('unknown location; ' + $location.path());
+                    $location.path("/error").search({});
                 }
+
             };

             return ErrorHandlingService;

==============================================================================
Revision: 6bddac945699
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Sat Feb  1 07:54:02 2014 UTC
Log:      + top banner has better action handling with all textual options

http://code.google.com/p/haiku-depot-web-app/source/detail?r=6bddac945699

Deleted:
 /haikudepotserver-webapp/src/main/webapp/img/logout.svg
Modified:
 /haikudepotserver-webapp/src/main/resources/spring/servlet-context.xml
 /haikudepotserver-webapp/src/main/webapp/css/banner.css
 /haikudepotserver-webapp/src/main/webapp/js/app/directive/banner.html
/haikudepotserver-webapp/src/main/webapp/js/app/directive/bannerdirective.js
 /haikudepotserver-webapp/src/main/webapp/js/app/service/userstateservice.js

=======================================
--- /haikudepotserver-webapp/src/main/webapp/img/logout.svg Mon Jan 13 10:50:43 2014 UTC
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg
-   xmlns:svg="http://www.w3.org/2000/svg";
-   xmlns="http://www.w3.org/2000/svg";
-   version="1.1"
-   width="17"
-   height="17"
-   id="svg2">
-  <g
-     transform="translate(0,-1035.3622)"
-     id="layer1">
-    <path
- d="m 16,8.416667 a 7.5833335,7.5833335 0 1 1 -15.16666651,0 7.5833335,7.5833335 0 1 1 15.16666651,0 z"
-       transform="matrix(0.9896907,0,0,0.9896907,0.17010294,1035.5323)"
-       id="path3755"
- style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
-    <path
-       d="M 6,11 11,6"
-       transform="translate(0,1035.3622)"
-       id="path3761"
- style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
-    <path
-       d="m 11,1046.3622 -5,-5"
-       id="path3763"
- style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
-  </g>
-</svg>
=======================================
--- /haikudepotserver-webapp/src/main/resources/spring/servlet-context.xml Mon Jan 13 10:50:43 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/spring/servlet-context.xml Sat Feb 1 07:54:02 2014 UTC
@@ -16,6 +16,7 @@
     </context:component-scan>

     <mvc:annotation-driven/>
+
     <mvc:resources mapping="/js/**" location="/js/" />
     <mvc:resources mapping="/css/**" location="/css/" />
     <mvc:resources mapping="/img/**" location="/img/" />
=======================================
--- /haikudepotserver-webapp/src/main/webapp/css/banner.css Fri Jan 31 10:26:48 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/css/banner.css Sat Feb 1 07:54:02 2014 UTC
@@ -41,16 +41,6 @@
     color: white;
     overflow: hidden;
 }
-
-#banner-user-logout {
-    display: inline;
-    float:right;
-}
-
-#banner-user-identification {
-    display: inline;
-    margin-right: 4px;
-}

 #banner-container a {
     color: white;
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/directive/banner.html Fri Jan 31 10:26:48 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/banner.html Sat Feb 1 07:54:02 2014 UTC
@@ -2,22 +2,10 @@
     <span id="banner-title" ng-click="goHome()">
         <div>Haiku Depot Server</div>
     </span>
-    <span id="banner-actions">
-        <span ng-show="canGoMore()">
-            <a href="" ng-click="goMore()">more</a> |
-        </span>
-        <span ng-show="canAuthenticateOrCreate()">
-            <a href="" ng-click="goAuthenticateUser()">login</a>
-            |
-            <a href="" ng-click="goCreateUser()">register</a>
-        </span>
-        <span ng-show="canShowAuthenticated()">
-            <div id="banner-user-logout">
- <a href="" ng-click="goLogoutUser()"><img src="img/logout.svg" alt="Logout"/></a>
-            </div>
-            <div id="banner-user-identification">
- <a href="" ng-click="goViewUser()">{{userDisplayTitle()}}</a>
-            </div>
+    <span id="banner-actions" ng-show="actions.length">
+        <span ng-repeat="a in actions">
+            <span ng-show="$index">|</span>
+            <a href="" ng-click="a.action()">{{a.title}}</a>
         </span>
     </span>
 </div>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/directive/bannerdirective.js Fri Jan 31 10:26:48 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/bannerdirective.js Sat Feb 1 07:54:02 2014 UTC
@@ -21,10 +21,28 @@
                     $rootScope,$scope,$log,$location,$route,
                     userState) {

-                    $scope.goMore = function() {
-                        $location.path('/more').search({});
-                        return false;
-                    }
+                    $scope.actions = [];
+ refreshActions(); // not direct assignment in case it later has to be a promise.
+
+ // when the page changes, the actions may change; for example, it is not appropriate to + // show the 'login' option when the user is presently logging in.
+
+                    $rootScope.$on(
+                        "$locationChangeSuccess",
+                        function(event, next, current) {
+                            refreshActions();
+                        }
+                    );
+
+ // when the user logs in or out then the actions may also change; for example, it makes + // no sense to show the logout button if nobody is presently logged in.
+
+                    $rootScope.$on(
+                        "userChangeSuccess",
+                        function(event, next, current) {
+                            refreshActions();
+                        }
+                    );

                     // This will take the user back to the home page.

@@ -33,53 +51,74 @@
                         return false;
                     }

-                    // --------------------------
-                    // USER / AUTHENTICATION
+                    function canGoMore() {
+                        var p = $location.path();
+                        return '/error' != p && '/more' != p;
+                    }

                     function isLocationPathDisablingUserState() {
                         var p = $location.path();
return '/error' == p || '/authenticateuser' == p | | '/createuser' == p;
                     }

-                    $scope.canGoMore = function() {
-                        var p = $location.path();
-                        return '/error' != p && '/more' != p;
-                    }
-
-                    $scope.canAuthenticateOrCreate = function() {
+                    function canAuthenticateOrCreate() {
return !isLocationPathDisablingUserState() && !userState.user();
                     }

-                    $scope.canShowAuthenticated = function() {
+                    function canShowAuthenticated() {
return !isLocationPathDisablingUserState() && userState.user()
                     }

-                    $scope.goViewUser = function() {
- $location.path('/viewuser/'+userState.user().nickname).search({});
-                        return false;
-                    }
+                    function refreshActions() {
+                        var a = [];
+
+                        if(canGoMore()) {
+                            a.push({
+                                title : 'more',
+                                action: function() {
+                                    $location.path('/more').search({});
+                                }
+                            });
+                        }
+
+                        if(canAuthenticateOrCreate()) {
+                            a.push({
+                                title : 'login',
+                                action: function() {
+                                    var p = $location.path();
+ $location.path('/authenticateuser').search( + _.extend($location.search(), { destination: p }));
+                                }
+                            });
+
+                            a.push({
+                                title : 'register',
+                                action: function() {
+ $location.path('/createuser').search({});
+                                }
+                            });
+                        }
+
+                        if(canShowAuthenticated()) {

-                    $scope.goCreateUser = function() {
-                        $location.path('/createuser').search({});
-                        return false;
-                    }
+                            a.push({
+                                title : userState.user().nickname,
+                                action: function() {
+ $location.path('/viewuser/'+userState.user().nickname).search({});
+                                }
+                            });

-                    $scope.goAuthenticateUser = function() {
-                        var p = $location.path();
-                        $location.path('/authenticateuser').search(
- _.extend($location.search(), { destination: p }));
-                        return false;
-                    }
+                            a.push({
+                                title : 'logout',
+                                action: function() {
+                                    userState.user(null);
+                                    $location.path('/').search({});
+                                }
+                            });

-                    $scope.goLogoutUser = function() {
-                        userState.user(null);
-                        $location.path('/').search({});
-                        return false;
-                    }
+                        }

-                    $scope.userDisplayTitle = function() {
-                        var data = userState.user();
-                        return data ? data.nickname : undefined;
+                        $scope.actions = a;
                     }

                 }
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/service/userstateservice.js Fri Jan 31 10:26:48 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/service/userstateservice.js Sat Feb 1 07:54:02 2014 UTC
@@ -11,8 +11,8 @@

 angular.module('haikudepotserver').factory('userState',
     [
-        '$log','$q','jsonRpc','pkgIcon',
-        function($log,$q,jsonRpc,pkgIcon) {
+        '$log','$q','$rootScope','jsonRpc','pkgIcon',
+        function($log,$q,$rootScope,jsonRpc,pkgIcon) {

             var user = undefined;

@@ -26,6 +26,9 @@

                 user : function(value) {
                     if(undefined !== value) {
+
+                        $rootScope.$broadcast('userChangeStart',value);
+
                         if(null==value) {
                             user = undefined;

@@ -54,6 +57,8 @@
                             user = value;
                             $log.info('have set user; '+user.nickname);
                         }
+
+                        $rootScope.$broadcast('userChangeSuccess',value);
                     }

                     return user;

==============================================================================
Revision: 01c6781a12ee
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Sun Feb  2 09:15:35 2014 UTC
Log:      + list repositories functionality

http://code.google.com/p/haiku-depot-web-app/source/detail?r=01c6781a12ee

Added:
/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApi.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/SearchRepositoriesRequest.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/SearchRepositoriesResult.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/AbstractSearchRequest.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/AbstractSearchResult.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApiImpl.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/PkgService.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/repository/RepositoryImportService.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/repository/RepositoryService.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/repository/controller/RepositoryImportController.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/repository/model/RepositorySearchSpecification.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/support/AbstractSearchSpecification.java /haikudepotserver-webapp/src/main/webapp/js/app/controller/listrepositories.html /haikudepotserver-webapp/src/main/webapp/js/app/controller/listrepositoriescontroller.js /haikudepotserver-webapp/src/main/webapp/js/app/directive/activeindicatordirective.js /haikudepotserver-webapp/src/main/webapp/js/app/directive/repositorylabeldirective.js /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotsever/api1/RepositoryApiIT.java
Deleted:
/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/PkgIconService.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/PkgImportService.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/PkgRepositoryImportService.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/PkgSearchService.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/controller/PkgRepositoryImportController.java
Modified:
/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetRuntimeInformationRequest.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetRuntimeInformationResult.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/SearchPkgsRequest.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/SearchPkgsResult.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/PkgApiImpl.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/controller/PkgIconController.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/model/PkgSearchSpecification.java
 /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml
 /haikudepotserver-webapp/src/main/webapp/css/haikudepotserver.css
 /haikudepotserver-webapp/src/main/webapp/css/home.css
 /haikudepotserver-webapp/src/main/webapp/js/app/constants.js
 /haikudepotserver-webapp/src/main/webapp/js/app/controller/home.html
/haikudepotserver-webapp/src/main/webapp/js/app/controller/homecontroller.js
 /haikudepotserver-webapp/src/main/webapp/js/app/controller/more.html
/haikudepotserver-webapp/src/main/webapp/js/app/controller/morecontroller.js
 /haikudepotserver-webapp/src/main/webapp/js/app/routes.js
/haikudepotserver-webapp/src/main/webapp/js/app/service/breadcrumbsservice.js /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotsever/api1/PkgApiIT.java /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotsever/api1/support/IntegrationTestSupportService.java

=======================================
--- /dev/null
+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApi.java Sun Feb 2 09:15:35 2014 UTC
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1;
+
+import com.googlecode.jsonrpc4j.JsonRpcService;
+import org.haikuos.haikudepotserver.api1.model.repository.SearchRepositoriesRequest; +import org.haikuos.haikudepotserver.api1.model.repository.SearchRepositoriesResult;
+
+@JsonRpcService("/api/v1/repository")
+public interface RepositoryApi {
+
+    /**
+ * <p>This method will search the repositories according to the supplied criteria and will + * return a list of those found. Any user is able to see the repositories.</p>
+     */
+
+ SearchRepositoriesResult searchRepositories(SearchRepositoriesRequest searchRepositoriesRequest);
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/SearchRepositoriesRequest.java Sun Feb 2 09:15:35 2014 UTC
@@ -0,0 +1,11 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.repository;
+
+import org.haikuos.haikudepotserver.api1.support.AbstractSearchRequest;
+
+public class SearchRepositoriesRequest extends AbstractSearchRequest {
+}
=======================================
--- /dev/null
+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/repository/SearchRepositoriesResult.java Sun Feb 2 09:15:35 2014 UTC
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.repository;
+
+import org.haikuos.haikudepotserver.api1.support.AbstractSearchResult;
+
+public class SearchRepositoriesResult extends AbstractSearchResult<SearchRepositoriesResult.Repository> {
+
+    public static class Repository {
+
+        public Boolean active;
+        public String code;
+        public String url;
+        public String architectureCode;
+        public Long createTimestamp;
+        public Long modifyTimestamp;
+
+    }
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/AbstractSearchRequest.java Sun Feb 2 09:15:35 2014 UTC
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.support;
+
+/**
+ * <p>Some of the API methods call for search functionality. This typically requires some common query parameters + * such as the offset into the list and the maximum number of results to return. This abstract class contains those
+ * values for re-use.</p>
+ */
+
+public abstract class AbstractSearchRequest {
+
+    public enum ExpressionType {
+        CONTAINS
+    }
+
+    public String expression;
+
+    public ExpressionType expressionType;
+
+    /**
+     * <P>This is the offset into the result list.</P>
+     */
+
+    public Integer offset;
+
+    /**
+ * <p>This is the maximum number of results that will be returned by the search.</p>
+     */
+
+    public Integer limit;
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/support/AbstractSearchResult.java Sun Feb 2 09:15:35 2014 UTC
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.support;
+
+import java.util.List;
+
+/**
+ * <p>Some of the API performs searches into various data sets such as packages. This abstract class is able to
+ * provide some common material for the returned data.</p>
+ */
+
+public abstract class AbstractSearchResult<T> {
+
+    /**
+     * <p>This is a list of the result objects.</p>
+     */
+
+    public List<T> items;
+
+    /**
+ * <p>If this is returned true then there are more results that could be obtained by increasing the limit of the
+     * number of results in the request.</p>
+     */
+
+    public Boolean hasMore;
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/RepositoryApiImpl.java Sun Feb 2 09:15:35 2014 UTC
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1;
+
+import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.configuration.server.ServerRuntime;
+import org.haikuos.haikudepotserver.api1.model.repository.SearchRepositoriesRequest; +import org.haikuos.haikudepotserver.api1.model.repository.SearchRepositoriesResult;
+import org.haikuos.haikudepotserver.dataobjects.Repository;
+import org.haikuos.haikudepotserver.pkg.model.PkgSearchSpecification;
+import org.haikuos.haikudepotserver.repository.RepositoryService;
+import org.haikuos.haikudepotserver.repository.model.RepositorySearchSpecification;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+@Component
+public class RepositoryApiImpl extends AbstractApiImpl implements RepositoryApi {
+
+    @Resource
+    ServerRuntime serverRuntime;
+
+    @Resource
+    RepositoryService repositoryService;
+
+    @Override
+ public SearchRepositoriesResult searchRepositories(SearchRepositoriesRequest request) {
+        Preconditions.checkNotNull(request);
+
+        final ObjectContext context = serverRuntime.getContext();
+
+ RepositorySearchSpecification specification = new RepositorySearchSpecification();
+        String exp = request.expression;
+
+        if(null!=exp) {
+            exp = Strings.emptyToNull(exp.trim().toLowerCase());
+        }
+
+        specification.setExpression(exp);
+
+        if(null!=request.expressionType) {
+            specification.setExpressionType(
+ PkgSearchSpecification.ExpressionType.valueOf(request.expressionType.name()));
+        }
+
+ specification.setLimit(request.limit+1); // get +1 to see if there are any more.
+        specification.setOffset(request.offset);
+
+        SearchRepositoriesResult result = new SearchRepositoriesResult();
+ List<Repository> searchedRepositories = repositoryService.search(context,specification);
+
+ // if there are more than we asked for then there must be more available.
+
+ result.hasMore = new Boolean(searchedRepositories.size() > request.limit);
+
+        if(result.hasMore) {
+ searchedRepositories = searchedRepositories.subList(0,request.limit);
+        }
+
+        result.items = Lists.newArrayList(Iterables.transform(
+                searchedRepositories,
+ new Function<Repository, SearchRepositoriesResult.Repository>() {
+                    @Override
+ public SearchRepositoriesResult.Repository apply(Repository input) { + SearchRepositoriesResult.Repository resultRepository = new SearchRepositoriesResult.Repository();
+                        resultRepository.active = input.getActive();
+ resultRepository.architectureCode = input.getArchitecture().getCode();
+                        resultRepository.code = input.getCode();
+ resultRepository.createTimestamp = input.getCreateTimestamp().getTime(); + resultRepository.modifyTimestamp = input.getModifyTimestamp().getTime();
+                        return resultRepository;
+                    }
+                }
+        ));
+
+        return result;
+    }
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/PkgService.java Sun Feb 2 09:15:35 2014 UTC
@@ -0,0 +1,395 @@
+/*
+ * Copyright 2013-2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.pkg;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.io.ByteStreams;
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.ObjectId;
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
+import org.apache.cayenne.query.EJBQLQuery;
+import org.apache.cayenne.query.SelectQuery;
+import org.haikuos.haikudepotserver.dataobjects.*;
+import org.haikuos.haikudepotserver.dataobjects.Pkg;
+import org.haikuos.haikudepotserver.dataobjects.PkgUrlType;
+import org.haikuos.haikudepotserver.dataobjects.PkgVersion;
+import org.haikuos.haikudepotserver.pkg.model.BadPkgIconException;
+import org.haikuos.haikudepotserver.pkg.model.PkgSearchSpecification;
+import org.haikuos.haikudepotserver.support.Closeables;
+import org.haikuos.haikudepotserver.support.ImageHelper;
+import org.haikuos.haikudepotserver.support.cayenne.LikeHelper;
+import org.haikuos.pkg.model.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * <p>This service undertakes non-trivial operations on packages.</p>
+ */
+
+@Service
+public class PkgService {
+
+ protected static Logger logger = LoggerFactory.getLogger(PkgService.class);
+
+    private ImageHelper imageHelper = new ImageHelper();
+
+    // ------------------------------
+    // SEARCH
+
+ public List<Pkg> search(ObjectContext context, PkgSearchSpecification search) {
+        Preconditions.checkNotNull(search);
+        Preconditions.checkNotNull(context);
+        Preconditions.checkState(search.getOffset() >= 0);
+        Preconditions.checkState(search.getLimit() > 0);
+        Preconditions.checkNotNull(search.getArchitectures());
+        Preconditions.checkState(!search.getArchitectures().isEmpty());
+
+        List<String> pkgNames;
+
+        // using jpql because of need to get out raw rows for the pkg name.
+
+        {
+            StringBuilder queryBuilder = new StringBuilder();
+            List<Object> parameters = Lists.newArrayList();
+ List<Architecture> architecturesList = Lists.newArrayList(search.getArchitectures());
+
+ queryBuilder.append("SELECT DISTINCT pv.pkg.name FROM PkgVersion pv WHERE");
+
+            queryBuilder.append(" (");
+
+            for(int i=0; i < architecturesList.size(); i++) {
+                if(0!=i) {
+                    queryBuilder.append(" OR");
+                }
+
+ queryBuilder.append(String.format(" pv.architecture.code = ?%d",parameters.size()+1));
+                parameters.add(architecturesList.get(i).getCode());
+            }
+
+            queryBuilder.append(")");
+
+            if(!search.getIncludeInactive()) {
+                queryBuilder.append(" AND");
+                queryBuilder.append(" pv.active = true");
+                queryBuilder.append(" AND");
+                queryBuilder.append(" pv.pkg.active = true");
+            }
+
+            if(!Strings.isNullOrEmpty(search.getExpression())) {
+                queryBuilder.append(" AND");
+ queryBuilder.append(String.format(" pv.pkg.name LIKE ?%d",parameters.size()+1)); + parameters.add("%" + LikeHelper.ESCAPER.escape(search.getExpression()) + "%");
+            }
+
+            queryBuilder.append(" ORDER BY pv.pkg.name ASC");
+
+            EJBQLQuery query = new EJBQLQuery(queryBuilder.toString());
+
+            for(int i=0;i<parameters.size();i++) {
+                query.setParameter(i+1,parameters.get(i));
+            }
+
+            // [apl 13.nov.2013]
+ // There seems to be a problem with the resolution of "IN" parameters; it doesn't seem to handle + // the collection in the parameter very well. See EJBQLConditionTranslator.processParameter(..)
+            // Seems to be a problem if it is a data object or a scalar.
+
+// queryBuilder.append("SELECT DISTINCT pv.pkg.name FROM PkgVersion pv WHERE"); +// //queryBuilder.append(" pv.architecture IN (:architectures)"); +// queryBuilder.append(" pv.architecture.code IN (:architectureCodes)");
+//            queryBuilder.append(" AND");
+//            queryBuilder.append(" pv.active=true");
+//            queryBuilder.append(" AND");
+//            queryBuilder.append(" pv.pkg.active=true");
+//
+//            if(!Strings.isNullOrEmpty(search.getExpression())) {
+//                queryBuilder.append(" AND");
+// queryBuilder.append(" pv.pkg.name LIKE :pkgNameLikeExpression");
+//            }
+//
+//            queryBuilder.append(" ORDER BY pv.pkg.name ASC");
+//
+//            EJBQLQuery query = new EJBQLQuery(queryBuilder.toString());
+//
+// //query.setParameter("architectures", search.getArchitectures());
+//            query.setParameter("architectureCodes", Iterables.transform(
+//                    search.getArchitectures(),
+//                    new Function<Architecture, String>() {
+//                        @Override
+//                        public String apply(Architecture architecture) {
+//                            return architecture.getCode();
+//                        }
+//                    }
+//            ));
+//
+//            if(!Strings.isNullOrEmpty(search.getExpression())) {
+// query.setParameter("pkgNameLikeExpression", "%" + LikeHelper.escapeExpression(search.getExpression()) + "%");
+//            }
+
+            query.setFetchOffset(search.getOffset());
+            query.setFetchLimit(search.getLimit());
+
+            pkgNames = (List<String>) context.performQuery(query);
+        }
+
+        List<Pkg> pkgs = Collections.emptyList();
+
+        if(0!=pkgNames.size()) {
+
+ SelectQuery query = new SelectQuery(Pkg.class, ExpressionFactory.inExp(Pkg.NAME_PROPERTY, pkgNames));
+
+            pkgs = Lists.newArrayList(context.performQuery(query));
+
+ // repeat the sort of the main query to get the packages back into order again.
+
+            Collections.sort(pkgs, new Comparator<Pkg>() {
+                @Override
+                public int compare(Pkg o1, Pkg o2) {
+                    return o1.getName().compareTo(o2.getName());
+                }
+            });
+        }
+
+        return pkgs;
+    }
+
+    // ------------------------------
+    // ICONS
+
+    private void writeGenericIconImage(
+            OutputStream output,
+            int size) throws IOException {
+
+        Preconditions.checkNotNull(output);
+        Preconditions.checkState(16==size||32==size);
+
+ String resource = String.format("/img/generic/generic%d.png", size);
+        InputStream inputStream = null;
+
+        try {
+            inputStream = this.getClass().getResourceAsStream(resource);
+
+            if(null==inputStream) {
+ throw new IllegalStateException(String.format("the resource; %s was not able to be found, but should be in the application build product", resource));
+            }
+            else {
+                ByteStreams.copy(inputStream, output);
+            }
+        }
+        finally {
+            Closeables.closeQuietly(inputStream);
+        }
+    }
+
+    /**
+ * <p>This method will write the package's icon to the supplied output stream. If there is no icon stored
+     * for the package then a generic icon will be provided instead.</p>
+     */
+
+    public void writePkgIconImage(
+            OutputStream output,
+            ObjectContext context,
+            Pkg pkg,
+            int size) throws IOException {
+
+        Preconditions.checkNotNull(output);
+        Preconditions.checkNotNull(context);
+        Preconditions.checkNotNull(pkg);
+        Preconditions.checkState(16==size||32==size);
+
+        Optional<PkgIconImage> pkgIconImageOptional = pkg.getPkgIconImage(
+ MediaType.getByCode(context, com.google.common.net.MediaType.PNG.toString()).get(),
+                size);
+
+        if(pkgIconImageOptional.isPresent()) {
+            output.write(pkgIconImageOptional.get().getData());
+        }
+
+        writeGenericIconImage(output, size);
+    }
+
+    /**
+ * <p>This method will write the PNG data supplied in the input to the package as its icon. Note that the icon + * must comply with necessary characteristics; for example it must be either 16 or 32 pixels along both its sides. + * If it is non-compliant then an instance of {@link org.haikuos.haikudepotserver.pkg.model.BadPkgIconException} will be thrown.</p>
+     */
+
+    public void storePkgIconImage(
+            InputStream input,
+            int expectedSize,
+            ObjectContext context,
+            Pkg pkg) throws IOException, BadPkgIconException {
+
+        Preconditions.checkNotNull(input);
+        Preconditions.checkNotNull(context);
+        Preconditions.checkNotNull(pkg);
+        Preconditions.checkState(16==expectedSize||32==expectedSize);
+
+        byte[] pngData = ByteStreams.toByteArray(input);
+        ImageHelper.Size size =  imageHelper.derivePngSize(pngData);
+
+ // check that the file roughly looks like PNG and that the size can be
+        // parsed and that the size fits the requirements for the icon.
+
+        if(null==size || (!size.areSides(16) && !size.areSides(32))) {
+ logger.warn("attempt to set the package icon for package {}, but the size was not able to be established; either it is not a valid png image or the size of the png image is not appropriate",pkg.getName());
+            throw new BadPkgIconException();
+        }
+
+        if(expectedSize != size.height && expectedSize != size.width) {
+ logger.warn("attempt to set the package icon for package {}, but the size was note the expected {}px",pkg.getName(),expectedSize);
+            throw new BadPkgIconException();
+        }
+
+ MediaType png = MediaType.getByCode(context, com.google.common.net.MediaType.PNG.toString()).get(); + Optional<PkgIconImage> pkgIconImageOptional = pkg.getPkgIconImage(png,size.width);
+        PkgIconImage pkgIconImage = null;
+
+        if(pkgIconImageOptional.isPresent()) {
+            pkgIconImage = pkgIconImageOptional.get();
+        }
+        else {
+            PkgIcon pkgIcon = context.newObject(PkgIcon.class);
+            pkg.addToManyTarget(Pkg.PKG_ICONS_PROPERTY, pkgIcon, true);
+            pkgIcon.setMediaType(png);
+            pkgIcon.setSize(size.width);
+            pkgIconImage = context.newObject(PkgIconImage.class);
+ pkgIcon.addToManyTarget(PkgIcon.PKG_ICON_IMAGES_PROPERTY, pkgIconImage, true);
+        }
+
+        pkgIconImage.setData(pngData);
+        pkg.setModifyTimestamp(new java.util.Date());
+
+ logger.info("the icon {}px for package {} has been updated", size.width, pkg.getName());
+    }
+
+    // ------------------------------
+    // IMPORT
+
+ private Expression toExpression(org.haikuos.pkg.model.PkgVersion version) {
+        return ExpressionFactory.matchExp(
+ org.haikuos.haikudepotserver.dataobjects.PkgVersion.MAJOR_PROPERTY, version.getMajor())
+                .andExp(ExpressionFactory.matchExp(
+ org.haikuos.haikudepotserver.dataobjects.PkgVersion.MINOR_PROPERTY, version.getMinor()))
+                .andExp(ExpressionFactory.matchExp(
+ org.haikuos.haikudepotserver.dataobjects.PkgVersion.MICRO_PROPERTY, version.getMicro()))
+                .andExp(ExpressionFactory.matchExp(
+ org.haikuos.haikudepotserver.dataobjects.PkgVersion.PRE_RELEASE_PROPERTY, version.getPreRelease()))
+                .andExp(ExpressionFactory.matchExp(
+ org.haikuos.haikudepotserver.dataobjects.PkgVersion.REVISION_PROPERTY, version.getRevision()));
+    }
+
+    /**
+ * <p>This method will import the package described by the {@paramref pkg} parameter by locating the package and
+     * either creating it or updating it as necessary.</p>
+     */
+
+    public void importFrom(
+            ObjectContext objectContext,
+            ObjectId repositoryObjectId,
+            org.haikuos.pkg.model.Pkg pkg) {
+
+        Preconditions.checkNotNull(pkg);
+        Preconditions.checkNotNull(repositoryObjectId);
+
+ Repository repository = Repository.get(objectContext, repositoryObjectId);
+
+        // first, check to see if the package is there or not.
+
+ Optional<org.haikuos.haikudepotserver.dataobjects.Pkg> persistedPkgOptional = org.haikuos.haikudepotserver.dataobjects.Pkg.getByName(objectContext, pkg.getName());
+        org.haikuos.haikudepotserver.dataobjects.Pkg persistedPkg;
+ org.haikuos.haikudepotserver.dataobjects.PkgVersion persistedPkgVersion = null;
+
+        if(!persistedPkgOptional.isPresent()) {
+ persistedPkg = objectContext.newObject(org.haikuos.haikudepotserver.dataobjects.Pkg.class);
+            persistedPkg.setName(pkg.getName());
+            persistedPkg.setActive(Boolean.TRUE);
+ logger.info("the package {} did not exist; will create",pkg.getName());
+        }
+        else {
+            persistedPkg = persistedPkgOptional.get();
+
+ // if we know that the package exists then we should look for the version.
+
+            SelectQuery selectQuery = new SelectQuery(
+ org.haikuos.haikudepotserver.dataobjects.PkgVersion.class,
+                    ExpressionFactory.matchExp(
+ org.haikuos.haikudepotserver.dataobjects.PkgVersion.PKG_PROPERTY,
+                            persistedPkg)
+                            .andExp(toExpression(pkg.getVersion())));
+
+            persistedPkgVersion = Iterables.getOnlyElement(
+ (List<org.haikuos.haikudepotserver.dataobjects.PkgVersion>) objectContext.performQuery(selectQuery),
+                    null);
+        }
+
+        if(null==persistedPkgVersion) {
+
+ persistedPkgVersion = objectContext.newObject(org.haikuos.haikudepotserver.dataobjects.PkgVersion.class);
+            persistedPkgVersion.setActive(Boolean.TRUE);
+            persistedPkgVersion.setMajor(pkg.getVersion().getMajor());
+            persistedPkgVersion.setMinor(pkg.getVersion().getMinor());
+            persistedPkgVersion.setMicro(pkg.getVersion().getMicro());
+ persistedPkgVersion.setPreRelease(pkg.getVersion().getPreRelease()); + persistedPkgVersion.setRevision(pkg.getVersion().getRevision());
+            persistedPkgVersion.setRepository(repository);
+            persistedPkgVersion.setArchitecture(Architecture.getByCode(
+                    objectContext,
+                    pkg.getArchitecture().name().toLowerCase()).get());
+            persistedPkgVersion.setPkg(persistedPkg);
+
+            // now add the copyrights
+            for(String copyright : pkg.getCopyrights()) {
+ PkgVersionCopyright persistedPkgVersionCopyright = objectContext.newObject(PkgVersionCopyright.class);
+                persistedPkgVersionCopyright.setBody(copyright);
+ persistedPkgVersionCopyright.setPkgVersion(persistedPkgVersion);
+            }
+
+            // now add the licenses
+            for(String license : pkg.getLicenses()) {
+ PkgVersionLicense persistedPkgVersionLicense = objectContext.newObject(PkgVersionLicense.class);
+                persistedPkgVersionLicense.setBody(license);
+ persistedPkgVersionLicense.setPkgVersion(persistedPkgVersion);
+            }
+
+            if(null!=pkg.getHomePageUrl()) {
+ PkgVersionUrl persistedPkgVersionUrl = objectContext.newObject(PkgVersionUrl.class); + persistedPkgVersionUrl.setUrl(pkg.getHomePageUrl().getUrl());
+                persistedPkgVersionUrl.setPkgUrlType(PkgUrlType.getByCode(
+                        objectContext,
+ pkg.getHomePageUrl().getUrlType().name().toLowerCase()).get());
+                persistedPkgVersionUrl.setPkgVersion(persistedPkgVersion);
+            }
+
+ if(!Strings.isNullOrEmpty(pkg.getSummary()) | | !Strings.isNullOrEmpty(pkg.getDescription())) { + PkgVersionLocalization persistedPkgVersionLocalization = objectContext.newObject(PkgVersionLocalization.class); + persistedPkgVersionLocalization.setDescription(pkg.getDescription()); + persistedPkgVersionLocalization.setSummary(pkg.getSummary()); + persistedPkgVersionLocalization.setPkgVersion(persistedPkgVersion);
+            }
+
+ logger.info("the version {} of package {} did not exist; will create", pkg.getVersion().toString(), pkg.getName());
+        }
+
+        logger.info("have processed package {}",pkg.toString());
+
+    }
+
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/repository/RepositoryImportService.java Sun Feb 2 09:15:35 2014 UTC
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2013-2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.repository;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Queues;
+import com.google.common.io.InputSupplier;
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.configuration.server.ServerRuntime;
+import org.haikuos.haikudepotserver.dataobjects.Repository;
+import org.haikuos.haikudepotserver.pkg.PkgService;
+import org.haikuos.haikudepotserver.pkg.model.PkgRepositoryImportJob;
+import org.haikuos.haikudepotserver.support.Closeables;
+import org.haikuos.pkg.PkgIterator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * <p>This object is responsible for migrating a HPKR file from a remote repository into the Haiku Depot Server + * database. It will copy the data into a local file and then work through it there. Note that this does + * not run the entire process in a single transaction; it will execute one transaction per package. So if the + * process fails, a repository update is likely to be partially imported.</p>
+ *
+ * <p>The system works by the caller lodging a request to update from a remote repository. The request may be + * later superceeded by another request for the same repository. When the import process has capacity then it
+ * will undertake the import process.</p>
+ */
+
+@Service
+public class RepositoryImportService {
+
+ protected static Logger logger = LoggerFactory.getLogger(RepositoryImportService.class);
+
+    public final static int SIZE_QUEUE = 10;
+
+    @Resource
+    ServerRuntime serverRuntime;
+
+    @Resource
+    PkgService pkgService;
+
+    private ThreadPoolExecutor executor = null;
+
+ private ArrayBlockingQueue<Runnable> runnables = Queues.newArrayBlockingQueue(SIZE_QUEUE);
+
+    private ThreadPoolExecutor getExecutor() {
+        if(null==executor) {
+            executor = new ThreadPoolExecutor(
+                    0, // core pool size
+                    1, // max pool size
+                    1l, // time to shutdown threads
+                    TimeUnit.MINUTES,
+                    runnables,
+                    new ThreadPoolExecutor.AbortPolicy());
+        }
+
+        return executor;
+    }
+
+    /**
+ * <p>This method will check that there is not already a job in the queue for this repository and then will + * add it to the queue so that it is run at some time in the future.</p>
+     * @param job
+     */
+
+    public void submit(final PkgRepositoryImportJob job) {
+        Preconditions.checkNotNull(job);
+
+ // first thing to do is to validate the request; does the repository exist and what is it's URL? + Optional<Repository> repositoryOptional = Repository.getByCode(serverRuntime.getContext(), job.getCode());
+
+        if(!repositoryOptional.isPresent()) {
+ throw new RuntimeException("unable to import repository data because repository was not able to be found for code; "+job.getCode());
+        }
+
+        if(!Iterables.tryFind(runnables, new Predicate<Runnable>() {
+            @Override
+            public boolean apply(java.lang.Runnable input) {
+ ImportRepositoryDataJobRunnable importRepositoryDataJobRunnable = (ImportRepositoryDataJobRunnable) input;
+                return importRepositoryDataJobRunnable.equals(job);
+            }
+        }).isPresent()) {
+ getExecutor().submit(new ImportRepositoryDataJobRunnable(this,job)); + logger.info("have submitted job to import repository data; {}", job.toString());
+        }
+        else {
+ logger.info("ignoring job to import repository data as there is already one waiting; {}", job.toString());
+
+        }
+    }
+
+    protected void run(PkgRepositoryImportJob job) {
+        Preconditions.checkNotNull(job);
+
+ Repository repository = Repository.getByCode(serverRuntime.getContext(), job.getCode()).get();
+        URL url;
+
+        try {
+            url = new URL(repository.getUrl());
+        }
+        catch(MalformedURLException mue) {
+ throw new IllegalStateException("the repository "+job.getCode()+" has a malformed url; "+repository.getUrl(),mue);
+        }
+
+ // now shift the URL's data into a temporary file and then process it.
+
+        File temporaryFile = null;
+        InputStream urlInputStream = null;
+
+        try {
+
+            urlInputStream = url.openStream();
+ temporaryFile = File.createTempFile(job.getCode()+"__import",".hpkr");
+            final InputStream finalUrlInputStream = urlInputStream;
+
+            com.google.common.io.Files.copy(
+                    new InputSupplier<InputStream>() {
+                        @Override
+                        public InputStream getInput() throws IOException {
+                            return finalUrlInputStream;
+                        }
+                    },
+                    temporaryFile);
+
+ logger.info("did copy data for repository {} ({}) to temporary file",job.getCode(),url.toString());
+
+ org.haikuos.pkg.HpkrFileExtractor fileExtractor = new org.haikuos.pkg.HpkrFileExtractor(temporaryFile); + PkgIterator pkgIterator = new PkgIterator(fileExtractor.getPackageAttributesIterator());
+
+            long startTimeMs = System.currentTimeMillis();
+ logger.info("will process data for repository {}",job.getCode());
+
+            while(pkgIterator.hasNext()) {
+ ObjectContext pkgImportContext = serverRuntime.getContext(); + pkgService.importFrom(pkgImportContext, repository.getObjectId(), pkgIterator.next());
+                pkgImportContext.commitChanges();
+            }
+
+ logger.info("did process data for repository {} in {}ms",job.getCode(),System.currentTimeMillis()-startTimeMs);
+
+        }
+        catch(Throwable th) {
+ logger.error("a problem has arisen processing a repository file for repository "+job.getCode()+" from url '"+url.toString()+"'",th);
+        }
+        finally {
+            Closeables.closeQuietly(urlInputStream);
+
+            if(null!=temporaryFile && temporaryFile.exists()) {
+                temporaryFile.delete();
+            }
+        }
+
+    }
+
+    /**
+ * <p>This is the object that gets enqueued to actually do the work.</p>
+     */
+
+ public static class ImportRepositoryDataJobRunnable implements Runnable {
+
+        private PkgRepositoryImportJob job;
+
+        private RepositoryImportService service;
+
+        public ImportRepositoryDataJobRunnable(
+                RepositoryImportService service,
+                PkgRepositoryImportJob job) {
+            Preconditions.checkNotNull(service);
+            Preconditions.checkNotNull(job);
+            this.service = service;
+            this.job = job;
+        }
+
+        @Override
+        public void run() {
+            service.run(job);
+        }
+
+    }
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/repository/RepositoryService.java Sun Feb 2 09:15:35 2014 UTC
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.repository;
+
+import com.google.common.base.Preconditions;
+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.Repository;
+import org.haikuos.haikudepotserver.repository.model.RepositorySearchSpecification;
+import org.haikuos.haikudepotserver.support.cayenne.LikeHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * <p>This service provides non-trivial operations and processes around repositories.</p>
+ */
+
+@Service
+public class RepositoryService {
+
+ protected static Logger logger = LoggerFactory.getLogger(RepositoryService.class);
+
+    // ------------------------------
+    // SEARCH
+
+ public List<Repository> search(ObjectContext context, RepositorySearchSpecification search) {
+        Preconditions.checkNotNull(search);
+        Preconditions.checkNotNull(context);
+        Preconditions.checkState(search.getOffset() >= 0);
+        Preconditions.checkState(search.getLimit() > 0);
+
+        List<Expression> expressions = Lists.newArrayList();
+
+        if(null!=search.getExpression()) {
+            switch(search.getExpressionType()) {
+                case CONTAINS:
+                    expressions.add(ExpressionFactory.likeExp(
+                            Repository.CODE_PROPERTY,
+ "%" + LikeHelper.ESCAPER.escape(search.getExpression()) + "%"));
+                    break;
+
+                default:
+ throw new IllegalStateException("unsupported expression type; "+search.getExpressionType());
+            }
+        }
+
+        if(!search.getIncludeInactive()) {
+ expressions.add(ExpressionFactory.matchExp(Repository.ACTIVE_PROPERTY, Boolean.TRUE));
+        }
+
+        Expression expression = null;
+
+        for(Expression e : expressions) {
+            if(null==expression) {
+                expression = e;
+            }
+            else {
+                expression = expression.andExp(e);
+            }
+        }
+
+ SelectQuery selectQuery = new SelectQuery(Repository.class, expression);
+        selectQuery.setFetchLimit(search.getLimit());
+        selectQuery.setFetchOffset(search.getOffset());
+ selectQuery.addOrdering(new Ordering(Repository.CODE_PROPERTY, SortOrder.ASCENDING));
+
+        return (List<Repository>) context.performQuery(selectQuery);
+    }
+
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/repository/controller/RepositoryImportController.java Sun Feb 2 09:15:35 2014 UTC
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2013-2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.repository.controller;
+
+import com.google.common.base.Strings;
+import com.google.common.net.HttpHeaders;
+import com.google.common.net.MediaType;
+import org.haikuos.haikudepotserver.repository.RepositoryImportService;
+import org.haikuos.haikudepotserver.pkg.model.PkgRepositoryImportJob;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * <p>This is the HTTP endpoint from which external systems are able to trigger a repository to be scanned for + * new packages by fetching the HPKR file and processing it. The actual logistics in this controller do not use + * typical Spring MVC error handling and so on; this is because fine control is required and this seems to be
+ * an easy way to achieve that; basically done manually.</p>
+ */
+
+@Controller
+@RequestMapping("/importrepositorydata")
+public class RepositoryImportController {
+
+ protected static Logger logger = LoggerFactory.getLogger(RepositoryImportController.class);
+
+    public final static String KEY_CODE = "code";
+
+    @Resource
+    RepositoryImportService importRepositoryDataService;
+
+    @RequestMapping(method = RequestMethod.GET)
+    public void fetch(
+            HttpServletResponse response,
+ @RequestParam(value = KEY_CODE, required = false) String repositoryCode) {
+
+        try {
+            if(Strings.isNullOrEmpty(repositoryCode)) {
+ logger.warn("attempt to import repository data service with no repository code supplied");
+                response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+ response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.PLAIN_TEXT_UTF_8.toString()); + response.getWriter().print(String.format("expected '%s' to have been a query argument to this resource\n",KEY_CODE));
+            }
+            else {
+ importRepositoryDataService.submit(new PkgRepositoryImportJob(repositoryCode));
+                response.setStatus(HttpServletResponse.SC_OK);
+ response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.PLAIN_TEXT_UTF_8.toString()); + response.getWriter().print(String.format("accepted import repository job for repository %s\n",repositoryCode));
+            }
+        }
+        catch(Throwable th) {
+            logger.error("failed to accept import repository job",th);
+
+ response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.PLAIN_TEXT_UTF_8.toString());
+
+            try {
+ response.getWriter().print(String.format("failed to accept import repository job for repository %s\n",repositoryCode));
+            }
+            catch(IOException ioe) {
+                /* ignore */
+            }
+        }
+    }
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/repository/model/RepositorySearchSpecification.java Sun Feb 2 09:15:35 2014 UTC
@@ -0,0 +1,6 @@
+package org.haikuos.haikudepotserver.repository.model;
+
+import org.haikuos.haikudepotserver.support.AbstractSearchSpecification;
+
+public class RepositorySearchSpecification extends AbstractSearchSpecification {
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/support/AbstractSearchSpecification.java Sun Feb 2 09:15:35 2014 UTC
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.support;
+
+/**
+ * <p>Various services are able to perform a search function to return lists of objects. In order to specify the + * search, those services would use an abstract subclass of this class. This class provides the basic search
+ * parameters that are re-used.</p>
+ */
+
+public abstract class AbstractSearchSpecification {
+
+    public enum ExpressionType {
+        CONTAINS
+    }
+
+    private String expression;
+
+    private ExpressionType expressionType;
+
+    /**
+     * <p>This is the offset into the list of results.</p>
+     */
+
+    private int offset = 0;
+
+    /**
+ * <p>This value will constrain the quantity of results to this limit.</p>
+     */
+
+    private int limit;
+
+    /**
+ * <p>Some data can contain active and inactive data. By default, the results will not contain inactive data.</p>
+     */
+
+    private boolean includeInactive = false;
+
+    public boolean getIncludeInactive() {
+        return includeInactive;
+    }
+
+    public void setIncludeInactive(boolean includeInactive) {
+        this.includeInactive = includeInactive;
+    }
+
+    public int getOffset() {
+        return offset;
+    }
+
+    public void setOffset(int offset) {
+        this.offset = offset;
+    }
+
+    public int getLimit() {
+        return limit;
+    }
+
+    public void setLimit(int value) {
+        this.limit = value;
+    }
+
+    public String getExpression() {
+        return expression;
+    }
+
+    public void setExpression(String expression) {
+        this.expression = expression;
+    }
+
+    public ExpressionType getExpressionType() {
+        return expressionType;
+    }
+
+    public void setExpressionType(ExpressionType expressionType) {
+        this.expressionType = expressionType;
+    }
+
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/listrepositories.html Sun Feb 2 09:15:35 2014 UTC
@@ -0,0 +1,53 @@
+<breadcrumbs items="breadcrumbItems"></breadcrumbs>
+
+<div class="content-container">
+
+    <div id="search-criteria-container">
+        <div>
+            <input
+                    ng-model="searchExpression"
+                    ui-keypress="{enter:'goSearch()'}"
+                    placeholder="Filter by code..."></input>
+            <button ng-click="goSearch()">Go</button>
+        </div>
+    </div>
+
+    <!-- RESULTS -->
+
+    <div id="search-results-container">
+
+ <div ng-show="repositories && 0==repositories.length" class="info-container">
+            <strong>No results; </strong>
+            There were no repositories able to be found.
+        </div>
+
+ <div ng-show="repositories && repositories.length" class="table-general-container">
+
+            <div class="table-general-pagination-container">
+ <a href="" ng-click="goPreviousPage()" ng-show="0!=offset">&laquo;</a>
+                <span ng-show="0==offset">&laquo;</span>
+ <a href="" ng-click="goNextPage()" ng-show="hasMore">&raquo;</a>
+                <span ng-show="!hasMore">&raquo;</span>
+            </div>
+
+            <table class="table-general">
+                <thead>
+                <th>Code</th>
+                <th>Architecture</th>
+                <th>Active</th>
+                </thead>
+                <tbody>
+                <tr ng-repeat="repository in repositories">
+ <td><repository-label repository="repository"></repository-label></td>
+                    <td><code>{{repository.architectureCode}}</code></td>
+ <td><active-indicator state="repository.active"></active-indicator></td>
+                </tr>
+                </tbody>
+            </table>
+
+        </div>
+    </div>
+</div>
+
+<div class="footer"></div>
+<spinner spin="shouldSpin()"></spinner>
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/listrepositoriescontroller.js Sun Feb 2 09:15:35 2014 UTC
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+angular.module('haikudepotserver').controller(
+    'ListRepositoriesController',
+    [
+        '$scope','$log','$location',
+        'jsonRpc','constants',
+        'breadcrumbs','errorHandling',
+        function(
+            $scope,$log,$location,
+            jsonRpc,constants,
+            breadcrumbs,errorHandling) {
+
+            $scope.breadcrumbItems = [
+                breadcrumbs.createMore(),
+                breadcrumbs.createListRepositories()
+            ];
+
+            const PAGESIZE = 14;
+
+            $scope.repositories = undefined;
+            $scope.hasMore = undefined;
+            $scope.offset = 0;
+
+            refetchRepositoriesAtFirstPage();
+
+            var amFetchingRepositories = false;
+
+            $scope.shouldSpin = function() {
+                return amFetchingRepositories;
+            }
+
+            // ---- PAGINATION
+
+            $scope.goSearch = function() {
+                    $scope.repositories = undefined;
+                refetchRepositoriesAtFirstPage();
+            }
+
+            function refetchRepositoriesAtFirstPage() {
+                $scope.offset = 0;
+                refetchRepositories();
+            }
+
+            function refetchRepositories() {
+
+                amFetchingRepositories = true;
+
+                jsonRpc.call(
+                        constants.ENDPOINT_API_V1_REPOSITORY,
+                        "searchRepositories",
+                        [{
+                            expression : $scope.searchExpression,
+                            expressionType : 'CONTAINS',
+                            offset : $scope.offset,
+                            limit : PAGESIZE
+                        }]
+                    ).then(
+                    function(result) {
+                        $scope.repositories = result.items;
+                        $scope.hasMore = result.hasMore;
+ $log.info('found '+result.items.length+' repositories');
+                        amFetchingRepositories = false;
+                    },
+                    function(err) {
+                        errorHandling.handleJsonRpcError(err);
+                    }
+                );
+
+            }
+
+            // ---- PAGINATION
+
+            $scope.goPreviousPage = function() {
+                if($scope.offset > 0) {
+                    $scope.offset -= PAGESIZE;
+                    refetchPkgs();
+                }
+
+                return false;
+            }
+
+            $scope.goNextPage = function() {
+                if($scope.hasMore) {
+                    $scope.offset += PAGESIZE;
+                    refetchPkgs();
+                }
+
+                return false;
+            }
+
+            $scope.classPreviousPage = function() {
+                return $scope.offset > 0 ? [] : ['disabled'];
+            }
+
+            $scope.classNextPage = function() {
+                return $scope.hasMore ? [] : ['disabled'];
+            }
+
+
+        }
+    ]
+);
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/activeindicatordirective.js Sun Feb 2 09:15:35 2014 UTC
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+/**
+ * <p>This directive renders a small element that indicates if the supplied value is active; for example, a green
+ * circle for active and a red circle for inactive.</p>
+ */
+
+angular.module('haikudepotserver').directive('activeIndicator',function() {
+    return {
+        restrict: 'E',
+        template:'<div ng-class="classes">&nbsp;</div>',
+        replace: true,
+        scope: {
+            state: '='
+        },
+        controller:
+            ['$scope',
+                function($scope) {
+
+                    $scope.classes = ['active-indicator'];
+
+                    $scope.$watch('state',function(newValue, oldValue) {
+                        $scope.classes = [
+                            'active-indicator',
+ newValue ? 'active-indicator-true' : 'inactive-indicator-false'
+                        ];
+                    });
+
+                }
+            ]
+    };
+});
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/repositorylabeldirective.js Sun Feb 2 09:15:35 2014 UTC
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+/**
+ * <p>This directive renders a small span which briefly describes the package.</p>
+ */
+
+angular.module('haikudepotserver').directive('repositoryLabel',function() {
+    return {
+        restrict: 'E',
+        template:'<span>{{repository.code}}</span>',
+        replace: true,
+        scope: {
+            repository: '='
+        },
+        controller:
+            ['$scope',
+                function($scope) {
+                }
+            ]
+    };
+});
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotsever/api1/RepositoryApiIT.java Sun Feb 2 09:15:35 2014 UTC
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotsever.api1;
+
+import org.fest.assertions.Assertions;
+import org.haikuos.haikudepotserver.api1.RepositoryApi;
+import org.haikuos.haikudepotserver.api1.model.pkg.SearchPkgsRequest;
+import org.haikuos.haikudepotserver.api1.model.repository.SearchRepositoriesRequest; +import org.haikuos.haikudepotserver.api1.model.repository.SearchRepositoriesResult;
+import org.haikuos.haikudepotsever.api1.support.AbstractIntegrationTest;
+import org.haikuos.haikudepotsever.api1.support.IntegrationTestSupportService;
+import org.junit.Test;
+
+import javax.annotation.Resource;
+
+public class RepositoryApiIT extends AbstractIntegrationTest {
+
+    @Resource
+    IntegrationTestSupportService integrationTestSupportService;
+
+    @Resource
+    RepositoryApi repositoryApi;
+
+    @Test
+    public void searchPkgsTest() {
+ IntegrationTestSupportService.StandardTestData data = integrationTestSupportService.createStandardTestData();
+
+ SearchRepositoriesRequest request = new SearchRepositoriesRequest();
+        request.expression = "test";
+        request.expressionType = SearchPkgsRequest.ExpressionType.CONTAINS;
+        request.limit = 2;
+        request.offset = 0;
+
+        // ------------------------------------
+ SearchRepositoriesResult result = repositoryApi.searchRepositories(request);
+        // ------------------------------------
+
+        Assertions.assertThat(result.hasMore).isFalse();
+        Assertions.assertThat(result.items.size()).isEqualTo(1);
+ Assertions.assertThat(result.items.get(0).code).isEqualTo("testrepository");
+    }
+
+}
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/PkgIconService.java Mon Jan 20 10:45:32 2014 UTC
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright 2013, Andrew Lindesay
- * Distributed under the terms of the MIT License.
- */
-
-package org.haikuos.haikudepotserver.pkg;
-
-import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
-import com.google.common.io.ByteStreams;
-import org.apache.cayenne.ObjectContext;
-import org.apache.cayenne.configuration.server.ServerRuntime;
-import org.haikuos.haikudepotserver.dataobjects.MediaType;
-import org.haikuos.haikudepotserver.dataobjects.Pkg;
-import org.haikuos.haikudepotserver.dataobjects.PkgIcon;
-import org.haikuos.haikudepotserver.dataobjects.PkgIconImage;
-import org.haikuos.haikudepotserver.pkg.model.BadPkgIconException;
-import org.haikuos.haikudepotserver.support.Closeables;
-import org.haikuos.haikudepotserver.support.ImageHelper;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.stereotype.Service;
-
-import javax.annotation.Resource;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-/**
- * <p>This service helps out with package icons.</p>
- */
-
-@Service
-public class PkgIconService {
-
- protected static Logger logger = LoggerFactory.getLogger(PkgIconService.class);
-
-    @Resource
-    ServerRuntime serverRuntime;
-
-    private ImageHelper imageHelper = new ImageHelper();
-
-    private void writeGenericIconImage(
-            OutputStream output,
-            int size) throws IOException {
-
-        Preconditions.checkNotNull(output);
-        Preconditions.checkState(16==size||32==size);
-
- String resource = String.format("/img/generic/generic%d.png", size);
-        InputStream inputStream = null;
-
-        try {
-            inputStream = this.getClass().getResourceAsStream(resource);
-
-            if(null==inputStream) {
- throw new IllegalStateException(String.format("the resource; %s was not able to be found, but should be in the application build product", resource));
-            }
-            else {
-                ByteStreams.copy(inputStream, output);
-            }
-        }
-        finally {
-            Closeables.closeQuietly(inputStream);
-        }
-    }
-
-    /**
- * <p>This method will write the package's icon to the supplied output stream. If there is no icon stored
-     * for the package then a generic icon will be provided instead.</p>
-     */
-
-    public void writePkgIconImage(
-            OutputStream output,
-            ObjectContext context,
-            Pkg pkg,
-            int size) throws IOException {
-
-        Preconditions.checkNotNull(output);
-        Preconditions.checkNotNull(context);
-        Preconditions.checkNotNull(pkg);
-        Preconditions.checkState(16==size||32==size);
-
-        Optional<PkgIconImage> pkgIconImageOptional = pkg.getPkgIconImage(
- MediaType.getByCode(context, com.google.common.net.MediaType.PNG.toString()).get(),
-                size);
-
-        if(pkgIconImageOptional.isPresent()) {
-            output.write(pkgIconImageOptional.get().getData());
-        }
-
-        writeGenericIconImage(output, size);
-    }
-
-    /**
- * <p>This method will write the PNG data supplied in the input to the package as its icon. Note that the icon - * must comply with necessary characteristics; for example it must be either 16 or 32 pixels along both its sides. - * If it is non-compliant then an instance of {@link BadPkgIconException} will be thrown.</p>
-     */
-
-    public void storePkgIconImage(
-            InputStream input,
-            int expectedSize,
-            ObjectContext context,
-            Pkg pkg) throws IOException, BadPkgIconException {
-
-        Preconditions.checkNotNull(input);
-        Preconditions.checkNotNull(context);
-        Preconditions.checkNotNull(pkg);
-        Preconditions.checkState(16==expectedSize||32==expectedSize);
-
-        byte[] pngData = ByteStreams.toByteArray(input);
-        ImageHelper.Size size =  imageHelper.derivePngSize(pngData);
-
- // check that the file roughly looks like PNG and that the size can be
-        // parsed and that the size fits the requirements for the icon.
-
-        if(null==size || (!size.areSides(16) && !size.areSides(32))) {
- logger.warn("attempt to set the package icon for package {}, but the size was not able to be established; either it is not a valid png image or the size of the png image is not appropriate",pkg.getName());
-            throw new BadPkgIconException();
-        }
-
-        if(expectedSize != size.height && expectedSize != size.width) {
- logger.warn("attempt to set the package icon for package {}, but the size was note the expected {}px",pkg.getName(),expectedSize);
-            throw new BadPkgIconException();
-        }
-
- MediaType png = MediaType.getByCode(context, com.google.common.net.MediaType.PNG.toString()).get(); - Optional<PkgIconImage> pkgIconImageOptional = pkg.getPkgIconImage(png,size.width);
-        PkgIconImage pkgIconImage = null;
-
-        if(pkgIconImageOptional.isPresent()) {
-            pkgIconImage = pkgIconImageOptional.get();
-        }
-        else {
-            PkgIcon pkgIcon = context.newObject(PkgIcon.class);
-            pkg.addToManyTarget(Pkg.PKG_ICONS_PROPERTY, pkgIcon, true);
-            pkgIcon.setMediaType(png);
-            pkgIcon.setSize(size.width);
-            pkgIconImage = context.newObject(PkgIconImage.class);
- pkgIcon.addToManyTarget(PkgIcon.PKG_ICON_IMAGES_PROPERTY, pkgIconImage, true);
-        }
-
-        pkgIconImage.setData(pngData);
-        pkg.setModifyTimestamp(new java.util.Date());
-
- logger.info("the icon {}px for package {} has been updated", size.width, pkg.getName());
-    }
-
-}
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/PkgImportService.java Wed Dec 11 08:25:33 2013 UTC
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright 2013, Andrew Lindesay
- * Distributed under the terms of the MIT License.
- */
-
-package org.haikuos.haikudepotserver.pkg;
-
-import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
-import com.google.common.base.Strings;
-import com.google.common.collect.Iterables;
-import org.apache.cayenne.ObjectContext;
-import org.apache.cayenne.ObjectId;
-import org.apache.cayenne.configuration.server.ServerRuntime;
-import org.apache.cayenne.exp.Expression;
-import org.apache.cayenne.exp.ExpressionFactory;
-import org.apache.cayenne.query.SelectQuery;
-import org.haikuos.haikudepotserver.dataobjects.*;
-import org.haikuos.pkg.model.Pkg;
-import org.haikuos.pkg.model.PkgVersion;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.stereotype.Service;
-
-import javax.annotation.Resource;
-import java.util.List;
-
-/**
- * <p>This class will import a package into the system's database; merging the data, or disabling entries as
- * necessary.</p>
- */
-
-@Service
-public class PkgImportService {
-
- protected static Logger logger = LoggerFactory.getLogger(PkgImportService.class);
-
-    private Expression toExpression(PkgVersion version) {
-        return ExpressionFactory.matchExp(
- org.haikuos.haikudepotserver.dataobjects.PkgVersion.MAJOR_PROPERTY, version.getMajor())
-                .andExp(ExpressionFactory.matchExp(
- org.haikuos.haikudepotserver.dataobjects.PkgVersion.MINOR_PROPERTY, version.getMinor()))
-                .andExp(ExpressionFactory.matchExp(
- org.haikuos.haikudepotserver.dataobjects.PkgVersion.MICRO_PROPERTY, version.getMicro()))
-                .andExp(ExpressionFactory.matchExp(
- org.haikuos.haikudepotserver.dataobjects.PkgVersion.PRE_RELEASE_PROPERTY, version.getPreRelease()))
-                .andExp(ExpressionFactory.matchExp(
- org.haikuos.haikudepotserver.dataobjects.PkgVersion.REVISION_PROPERTY, version.getRevision()));
-    }
-
-    /**
-     * <p>This is the Cayenne environment handle.</p>
-     */
-
-    @Resource
-    ServerRuntime serverRuntime;
-
-    /**
- * <p>This method will import the pkg described. The repository is also provided as a Cayenne object id in order - * to provide reference to the repository from which this pkg was obtained. Note that this method will execute
-     * as one 'transaction' (in the Cayenne sense).</p>
-     */
-
-    public void run(
-            ObjectId repositoryObjectId,
-            Pkg pkg) {
-
-        Preconditions.checkNotNull(pkg);
-        Preconditions.checkNotNull(repositoryObjectId);
-
-        ObjectContext objectContext = serverRuntime.getContext();
- Repository repository = Repository.get(objectContext, repositoryObjectId);
-
-        // first, check to see if the package is there or not.
-
- Optional<org.haikuos.haikudepotserver.dataobjects.Pkg> persistedPkgOptional = org.haikuos.haikudepotserver.dataobjects.Pkg.getByName(objectContext, pkg.getName());
-        org.haikuos.haikudepotserver.dataobjects.Pkg persistedPkg;
- org.haikuos.haikudepotserver.dataobjects.PkgVersion persistedPkgVersion = null;
-
-        if(!persistedPkgOptional.isPresent()) {
- persistedPkg = objectContext.newObject(org.haikuos.haikudepotserver.dataobjects.Pkg.class);
-            persistedPkg.setName(pkg.getName());
-            persistedPkg.setActive(Boolean.TRUE);
- logger.info("the package {} did not exist; will create",pkg.getName());
-        }
-        else {
-            persistedPkg = persistedPkgOptional.get();
-
- // if we know that the package exists then we should look for the version.
-
-            SelectQuery selectQuery = new SelectQuery(
- org.haikuos.haikudepotserver.dataobjects.PkgVersion.class,
-                    ExpressionFactory.matchExp(
- org.haikuos.haikudepotserver.dataobjects.PkgVersion.PKG_PROPERTY,
-                            persistedPkg)
-                    .andExp(toExpression(pkg.getVersion())));
-
-            persistedPkgVersion = Iterables.getOnlyElement(
- (List<org.haikuos.haikudepotserver.dataobjects.PkgVersion>) objectContext.performQuery(selectQuery),
-                    null);
-        }
-
-        if(null==persistedPkgVersion) {
-
- persistedPkgVersion = objectContext.newObject(org.haikuos.haikudepotserver.dataobjects.PkgVersion.class);
-            persistedPkgVersion.setActive(Boolean.TRUE);
-            persistedPkgVersion.setMajor(pkg.getVersion().getMajor());
-            persistedPkgVersion.setMinor(pkg.getVersion().getMinor());
-            persistedPkgVersion.setMicro(pkg.getVersion().getMicro());
- persistedPkgVersion.setPreRelease(pkg.getVersion().getPreRelease()); - persistedPkgVersion.setRevision(pkg.getVersion().getRevision());
-            persistedPkgVersion.setRepository(repository);
-            persistedPkgVersion.setArchitecture(Architecture.getByCode(
-                    objectContext,
-                    pkg.getArchitecture().name().toLowerCase()).get());
-            persistedPkgVersion.setPkg(persistedPkg);
-
-            // now add the copyrights
-            for(String copyright : pkg.getCopyrights()) {
- PkgVersionCopyright persistedPkgVersionCopyright = objectContext.newObject(PkgVersionCopyright.class);
-                persistedPkgVersionCopyright.setBody(copyright);
- persistedPkgVersionCopyright.setPkgVersion(persistedPkgVersion);
-            }
-
-            // now add the licenses
-            for(String license : pkg.getLicenses()) {
- PkgVersionLicense persistedPkgVersionLicense = objectContext.newObject(PkgVersionLicense.class);
-                persistedPkgVersionLicense.setBody(license);
- persistedPkgVersionLicense.setPkgVersion(persistedPkgVersion);
-            }
-
-            if(null!=pkg.getHomePageUrl()) {
- PkgVersionUrl persistedPkgVersionUrl = objectContext.newObject(PkgVersionUrl.class); - persistedPkgVersionUrl.setUrl(pkg.getHomePageUrl().getUrl());
-                persistedPkgVersionUrl.setPkgUrlType(PkgUrlType.getByCode(
-                        objectContext,
- pkg.getHomePageUrl().getUrlType().name().toLowerCase()).get());
-                persistedPkgVersionUrl.setPkgVersion(persistedPkgVersion);
-            }
-
- if(!Strings.isNullOrEmpty(pkg.getSummary()) | | !Strings.isNullOrEmpty(pkg.getDescription())) { - PkgVersionLocalization persistedPkgVersionLocalization = objectContext.newObject(PkgVersionLocalization.class); - persistedPkgVersionLocalization.setDescription(pkg.getDescription()); - persistedPkgVersionLocalization.setSummary(pkg.getSummary()); - persistedPkgVersionLocalization.setPkgVersion(persistedPkgVersion);
-            }
-
- logger.info("the version {} of package {} did not exist; will create", pkg.getVersion().toString(), pkg.getName());
-        }
-
-        objectContext.commitChanges();
-
-        logger.info("have processed package {}",pkg.toString());
-
-    }
-
-}
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/PkgRepositoryImportService.java Wed Dec 11 08:25:33 2013 UTC
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Copyright 2013, Andrew Lindesay
- * Distributed under the terms of the MIT License.
- */
-
-package org.haikuos.haikudepotserver.pkg;
-
-import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
-import com.google.common.base.Predicate;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Queues;
-import com.google.common.io.InputSupplier;
-import org.apache.cayenne.configuration.server.ServerRuntime;
-import org.haikuos.haikudepotserver.dataobjects.Repository;
-import org.haikuos.haikudepotserver.pkg.model.PkgRepositoryImportJob;
-import org.haikuos.haikudepotserver.support.Closeables;
-import org.haikuos.pkg.PkgIterator;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.stereotype.Service;
-
-import javax.annotation.Resource;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
-/**
- * <p>This object is responsible for migrating a HPKR file from a remote repository into the Haiku Depot Server - * database. It will copy the data into a local file and then work through it there. Note that this does - * not run the entire process in a single transaction; it will execute one transaction per package. So if the - * process fails, a repository update is likely to be partially imported.</p>
- *
- * <p>The system works by the caller lodging a request to update from a remote repository. The request may be - * later superceeded by another request for the same repository. When the import process has capacity then it
- * will undertake the import process.</p>
- */
-
-@Service
-public class PkgRepositoryImportService {
-
- protected static Logger logger = LoggerFactory.getLogger(PkgRepositoryImportService.class);
-
-    public final static int SIZE_QUEUE = 10;
-
-    @Resource
-    ServerRuntime serverRuntime;
-
-    @Resource
-    PkgImportService importPackageService;
-
-    private ThreadPoolExecutor executor = null;
-
- private ArrayBlockingQueue<Runnable> runnables = Queues.newArrayBlockingQueue(SIZE_QUEUE);
-
-    private ThreadPoolExecutor getExecutor() {
-        if(null==executor) {
-            executor = new ThreadPoolExecutor(
-                    0, // core pool size
-                    1, // max pool size
-                    1l, // time to shutdown threads
-                    TimeUnit.MINUTES,
-                    runnables,
-                    new ThreadPoolExecutor.AbortPolicy());
-        }
-
-        return executor;
-    }
-
-    /**
- * <p>This method will check that there is not already a job in the queue for this repository and then will - * add it to the queue so that it is run at some time in the future.</p>
-     * @param job
-     */
-
-    public void submit(final PkgRepositoryImportJob job) {
-        Preconditions.checkNotNull(job);
-
- // first thing to do is to validate the request; does the repository exist and what is it's URL? - Optional<Repository> repositoryOptional = Repository.getByCode(serverRuntime.getContext(), job.getCode());
-
-        if(!repositoryOptional.isPresent()) {
- throw new RuntimeException("unable to import repository data because repository was not able to be found for code; "+job.getCode());
-        }
-
-        if(!Iterables.tryFind(runnables, new Predicate<Runnable>() {
-            @Override
-            public boolean apply(java.lang.Runnable input) {
- ImportRepositoryDataJobRunnable importRepositoryDataJobRunnable = (ImportRepositoryDataJobRunnable) input;
-                return importRepositoryDataJobRunnable.equals(job);
-            }
-        }).isPresent()) {
- getExecutor().submit(new ImportRepositoryDataJobRunnable(this,job)); - logger.info("have submitted job to import repository data; {}", job.toString());
-        }
-        else {
- logger.info("ignoring job to import repository data as there is already one waiting; {}", job.toString());
-
-        }
-    }
-
-    protected void run(PkgRepositoryImportJob job) {
-        Preconditions.checkNotNull(job);
-
- Repository repository = Repository.getByCode(serverRuntime.getContext(), job.getCode()).get();
-        URL url;
-
-        try {
-            url = new URL(repository.getUrl());
-        }
-        catch(MalformedURLException mue) {
- throw new IllegalStateException("the repository "+job.getCode()+" has a malformed url; "+repository.getUrl(),mue);
-        }
-
- // now shift the URL's data into a temporary file and then process it.
-
-        File temporaryFile = null;
-        InputStream urlInputStream = null;
-
-        try {
-
-            urlInputStream = url.openStream();
- temporaryFile = File.createTempFile(job.getCode()+"__import",".hpkr");
-            final InputStream finalUrlInputStream = urlInputStream;
-
-            com.google.common.io.Files.copy(
-                    new InputSupplier<InputStream>() {
-                        @Override
-                        public InputStream getInput() throws IOException {
-                            return finalUrlInputStream;
-                        }
-                    },
-                    temporaryFile);
-
- logger.info("did copy data for repository {} ({}) to temporary file",job.getCode(),url.toString());
-
- org.haikuos.pkg.HpkrFileExtractor fileExtractor = new org.haikuos.pkg.HpkrFileExtractor(temporaryFile); - PkgIterator pkgIterator = new PkgIterator(fileExtractor.getPackageAttributesIterator());
-
-            long startTimeMs = System.currentTimeMillis();
- logger.info("will process data for repository {}",job.getCode());
-
-            while(pkgIterator.hasNext()) {
- importPackageService.run(repository.getObjectId(), pkgIterator.next());
-            }
-
- logger.info("did process data for repository {} in {}ms",job.getCode(),System.currentTimeMillis()-startTimeMs);
-
-        }
-        catch(Throwable th) {
- logger.error("a problem has arisen processing a repository file for repository "+job.getCode()+" from url '"+url.toString()+"'",th);
-        }
-        finally {
-            Closeables.closeQuietly(urlInputStream);
-
-            if(null!=temporaryFile && temporaryFile.exists()) {
-                temporaryFile.delete();
-            }
-        }
-
-    }
-
-    /**
- * <p>This is the object that gets enqueued to actually do the work.</p>
-     */
-
- public static class ImportRepositoryDataJobRunnable implements Runnable {
-
-        private PkgRepositoryImportJob job;
-
-        private PkgRepositoryImportService service;
-
-        public ImportRepositoryDataJobRunnable(
-                PkgRepositoryImportService service,
-                PkgRepositoryImportJob job) {
-            Preconditions.checkNotNull(service);
-            Preconditions.checkNotNull(job);
-            this.service = service;
-            this.job = job;
-        }
-
-        @Override
-        public void run() {
-            service.run(job);
-        }
-
-    }
-
-}
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/PkgSearchService.java Wed Dec 11 08:25:33 2013 UTC
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright 2013, Andrew Lindesay
- * Distributed under the terms of the MIT License.
- */
-
-package org.haikuos.haikudepotserver.pkg;
-
-import com.google.common.base.Preconditions;
-import com.google.common.base.Strings;
-import com.google.common.collect.Lists;
-import org.apache.cayenne.ObjectContext;
-import org.apache.cayenne.exp.ExpressionFactory;
-import org.apache.cayenne.query.EJBQLQuery;
-import org.apache.cayenne.query.SelectQuery;
-import org.haikuos.haikudepotserver.dataobjects.Architecture;
-import org.haikuos.haikudepotserver.dataobjects.Pkg;
-import org.haikuos.haikudepotserver.pkg.model.PkgSearchSpecification;
-import org.haikuos.haikudepotserver.support.cayenne.LikeHelper;
-import org.springframework.stereotype.Service;
-
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
-/**
- * <p>This service affords the ability for clients to search for packages in a standardized manner.</p>
- */
-
-@Service
-public class PkgSearchService {
-
- public List<Pkg> search(ObjectContext context, PkgSearchSpecification search) {
-        Preconditions.checkNotNull(search);
-        Preconditions.checkNotNull(context);
-        Preconditions.checkState(search.getOffset() >= 0);
-        Preconditions.checkState(search.getLimit() > 0);
-        Preconditions.checkNotNull(search.getArchitectures());
-        Preconditions.checkState(!search.getArchitectures().isEmpty());
-
-        List<String> pkgNames;
-
-        // using jpql because of need to get out raw rows for the pkg name.
-
-        {
-            StringBuilder queryBuilder = new StringBuilder();
-            List<Object> parameters = Lists.newArrayList();
- List<Architecture> architecturesList = Lists.newArrayList(search.getArchitectures());
-
- queryBuilder.append("SELECT DISTINCT pv.pkg.name FROM PkgVersion pv WHERE");
-
-            queryBuilder.append(" (");
-
-            for(int i=0; i < architecturesList.size(); i++) {
-                if(0!=i) {
-                    queryBuilder.append(" OR");
-                }
-
- queryBuilder.append(String.format(" pv.architecture.code = ?%d",parameters.size()+1));
-                parameters.add(architecturesList.get(i).getCode());
-            }
-
-            queryBuilder.append(")");
-
-            queryBuilder.append(" AND");
-            queryBuilder.append(" pv.active = true");
-            queryBuilder.append(" AND");
-            queryBuilder.append(" pv.pkg.active = true");
-
-            if(!Strings.isNullOrEmpty(search.getExpression())) {
-                queryBuilder.append(" AND");
- queryBuilder.append(String.format(" pv.pkg.name LIKE ?%d",parameters.size()+1)); - parameters.add("%" + LikeHelper.ESCAPER.escape(search.getExpression()) + "%");
-            }
-
-            queryBuilder.append(" ORDER BY pv.pkg.name ASC");
-
-            EJBQLQuery query = new EJBQLQuery(queryBuilder.toString());
-
-            for(int i=0;i<parameters.size();i++) {
-                query.setParameter(i+1,parameters.get(i));
-            }
-
-            // [apl 13.nov.2013]
- // There seems to be a problem with the resolution of "IN" parameters; it doesn't seem to handle - // the collection in the parameter very well. See EJBQLConditionTranslator.processParameter(..)
-            // Seems to be a problem if it is a data object or a scalar.
-
-// queryBuilder.append("SELECT DISTINCT pv.pkg.name FROM PkgVersion pv WHERE"); -// //queryBuilder.append(" pv.architecture IN (:architectures)"); -// queryBuilder.append(" pv.architecture.code IN (:architectureCodes)");
-//            queryBuilder.append(" AND");
-//            queryBuilder.append(" pv.active=true");
-//            queryBuilder.append(" AND");
-//            queryBuilder.append(" pv.pkg.active=true");
-//
-//            if(!Strings.isNullOrEmpty(search.getExpression())) {
-//                queryBuilder.append(" AND");
-// queryBuilder.append(" pv.pkg.name LIKE :pkgNameLikeExpression");
-//            }
-//
-//            queryBuilder.append(" ORDER BY pv.pkg.name ASC");
-//
-//            EJBQLQuery query = new EJBQLQuery(queryBuilder.toString());
-//
-// //query.setParameter("architectures", search.getArchitectures());
-//            query.setParameter("architectureCodes", Iterables.transform(
-//                    search.getArchitectures(),
-//                    new Function<Architecture, String>() {
-//                        @Override
-//                        public String apply(Architecture architecture) {
-//                            return architecture.getCode();
-//                        }
-//                    }
-//            ));
-//
-//            if(!Strings.isNullOrEmpty(search.getExpression())) {
-// query.setParameter("pkgNameLikeExpression", "%" + LikeHelper.escapeExpression(search.getExpression()) + "%");
-//            }
-
-            query.setFetchOffset(search.getOffset());
-            query.setFetchLimit(search.getLimit());
-
-            pkgNames = (List<String>) context.performQuery(query);
-        }
-
-        List<Pkg> pkgs = Collections.emptyList();
-
-        if(0!=pkgNames.size()) {
-
- SelectQuery query = new SelectQuery(Pkg.class, ExpressionFactory.inExp(Pkg.NAME_PROPERTY, pkgNames));
-
-            pkgs = Lists.newArrayList(context.performQuery(query));
-
- // repeat the sort of the main query to get the packages back into order again.
-
-            Collections.sort(pkgs, new Comparator<Pkg>() {
-                @Override
-                public int compare(Pkg o1, Pkg o2) {
-                    return o1.getName().compareTo(o2.getName());
-                }
-            });
-        }
-
-        return pkgs;
-    }
-
-}
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/controller/PkgRepositoryImportController.java Wed Dec 11 08:25:33 2013 UTC
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright 2013, Andrew Lindesay
- * Distributed under the terms of the MIT License.
- */
-
-package org.haikuos.haikudepotserver.pkg.controller;
-
-import com.google.common.base.Strings;
-import com.google.common.net.HttpHeaders;
-import com.google.common.net.MediaType;
-import org.haikuos.haikudepotserver.pkg.PkgRepositoryImportService;
-import org.haikuos.haikudepotserver.pkg.model.PkgRepositoryImportJob;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.bind.annotation.RequestParam;
-
-import javax.annotation.Resource;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-
-/**
- * <p>This is the HTTP endpoint from which external systems are able to trigger a repository to be scanned for - * new packages by fetching the HPKR file and processing it. The actual logistics in this controller do not use - * typical Spring MVC error handling and so on; this is because fine control is required and this seems to be
- * an easy way to achieve that; basically done manually.</p>
- */
-
-@Controller
-@RequestMapping("/importrepositorydata")
-public class PkgRepositoryImportController {
-
- protected static Logger logger = LoggerFactory.getLogger(PkgRepositoryImportController.class);
-
-    public final static String KEY_CODE = "code";
-
-    @Resource
-    PkgRepositoryImportService importRepositoryDataService;
-
-    @RequestMapping(method = RequestMethod.GET)
-    public void fetch(
-            HttpServletResponse response,
- @RequestParam(value = KEY_CODE, required = false) String repositoryCode) {
-
-        try {
-            if(Strings.isNullOrEmpty(repositoryCode)) {
- logger.warn("attempt to import repository data service with no repository code supplied");
-                response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
- response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.PLAIN_TEXT_UTF_8.toString()); - response.getWriter().print(String.format("expected '%s' to have been a query argument to this resource\n",KEY_CODE));
-            }
-            else {
- importRepositoryDataService.submit(new PkgRepositoryImportJob(repositoryCode));
-                response.setStatus(HttpServletResponse.SC_OK);
- response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.PLAIN_TEXT_UTF_8.toString()); - response.getWriter().print(String.format("accepted import repository job for repository %s\n",repositoryCode));
-            }
-        }
-        catch(Throwable th) {
-            logger.error("failed to accept import repository job",th);
-
- response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.PLAIN_TEXT_UTF_8.toString());
-
-            try {
- response.getWriter().print(String.format("failed to accept import repository job for repository %s\n",repositoryCode));
-            }
-            catch(IOException ioe) {
-                /* ignore */
-            }
-        }
-    }
-
-}
=======================================
--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetRuntimeInformationRequest.java Fri Jan 31 10:26:48 2014 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetRuntimeInformationRequest.java Sun Feb 2 09:15:35 2014 UTC
@@ -1,3 +1,8 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
 package org.haikuos.haikudepotserver.api1.model.miscellaneous;

 public class GetRuntimeInformationRequest {
=======================================
--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetRuntimeInformationResult.java Fri Jan 31 10:26:48 2014 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/miscellaneous/GetRuntimeInformationResult.java Sun Feb 2 09:15:35 2014 UTC
@@ -1,3 +1,8 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
 package org.haikuos.haikudepotserver.api1.model.miscellaneous;

 public class GetRuntimeInformationResult {
=======================================
--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/SearchPkgsRequest.java Fri Nov 15 08:51:45 2013 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/SearchPkgsRequest.java Sun Feb 2 09:15:35 2014 UTC
@@ -5,25 +5,14 @@

 package org.haikuos.haikudepotserver.api1.model.pkg;

+import org.haikuos.haikudepotserver.api1.support.AbstractSearchRequest;
+
 /**
* <p>This is the model object that is used to define the request to search for packages in the system.</p>
  */

-public class SearchPkgsRequest {
-
-    public enum ExpressionType {
-        CONTAINS
-    }
-
-    public String expression;
+public class SearchPkgsRequest extends AbstractSearchRequest {

     public String architectureCode;

-    public ExpressionType expressionType;
-
-    public Integer offset;
-
-    public Integer limit;
-
-
 }
=======================================
--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/SearchPkgsResult.java Thu Dec 5 09:23:22 2013 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/SearchPkgsResult.java Sun Feb 2 09:15:35 2014 UTC
@@ -1,17 +1,13 @@
 /*
- * Copyright 2013, Andrew Lindesay
+ * Copyright 2013-2014, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

 package org.haikuos.haikudepotserver.api1.model.pkg;

-import java.util.List;
-
-public class SearchPkgsResult {
-
-    public List<Pkg> pkgs;
+import org.haikuos.haikudepotserver.api1.support.AbstractSearchResult;

-    public Boolean hasMore;
+public class SearchPkgsResult extends AbstractSearchResult<SearchPkgsResult.Pkg> {

     public static class Pkg {
         public String name;
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/PkgApiImpl.java Wed Dec 11 08:25:33 2013 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/PkgApiImpl.java Sun Feb 2 09:15:35 2014 UTC
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013, Andrew Lindesay
+ * Copyright 2013-2014, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

@@ -19,7 +19,7 @@
import org.haikuos.haikudepotserver.api1.support.AuthorizationFailureException;
 import org.haikuos.haikudepotserver.api1.support.ObjectNotFoundException;
 import org.haikuos.haikudepotserver.dataobjects.*;
-import org.haikuos.haikudepotserver.pkg.PkgSearchService;
+import org.haikuos.haikudepotserver.pkg.PkgService;
 import org.haikuos.haikudepotserver.pkg.model.PkgSearchSpecification;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -42,7 +42,7 @@
     ServerRuntime serverRuntime;

     @Resource
-    PkgSearchService searchPkgsService;
+    PkgService pkgService;

     @Override
     public SearchPkgsResult searchPkgs(SearchPkgsRequest request) {
@@ -84,7 +84,7 @@
         specification.setOffset(request.offset);

         SearchPkgsResult result = new SearchPkgsResult();
- List<Pkg> searchedPkgs = searchPkgsService.search(context,specification);
+        List<Pkg> searchedPkgs = pkgService.search(context,specification);

// if there are more than we asked for then there must be more available.

@@ -94,7 +94,7 @@
             searchedPkgs = searchedPkgs.subList(0,request.limit);
         }

-        result.pkgs = Lists.newArrayList(Iterables.transform(
+        result.items = Lists.newArrayList(Iterables.transform(
                 searchedPkgs,
                 new Function<Pkg, SearchPkgsResult.Pkg>() {
                     @Override
@@ -142,6 +142,7 @@
         version.revision = pkgVersion.getRevision();
         version.preRelease = pkgVersion.getPreRelease();

+        version.repositoryCode = pkgVersion.getRepository().getCode();
         version.architectureCode = pkgVersion.getArchitecture().getCode();
         version.copyrights = Lists.transform(
                 pkgVersion.getPkgVersionCopyrights(),
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/controller/PkgIconController.java Mon Jan 20 10:45:32 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/controller/PkgIconController.java Sun Feb 2 09:15:35 2014 UTC
@@ -16,7 +16,7 @@
import org.haikuos.haikudepotserver.web.controller.WebResourceGroupController;
 import org.haikuos.haikudepotserver.dataobjects.Pkg;
 import org.haikuos.haikudepotserver.dataobjects.User;
-import org.haikuos.haikudepotserver.pkg.PkgIconService;
+import org.haikuos.haikudepotserver.pkg.PkgService;
 import org.haikuos.haikudepotserver.pkg.model.BadPkgIconException;
 import org.haikuos.haikudepotserver.support.web.AbstractController;
 import org.slf4j.Logger;
@@ -51,7 +51,7 @@
     ServerRuntime serverRuntime;

     @Resource
-    PkgIconService pkgIconService;
+    PkgService pkgService;

@RequestMapping(value = "/{"+KEY_PKGNAME+"}.{"+KEY_FORMAT+"}", method = RequestMethod.HEAD)
     public void fetchHead(
@@ -83,7 +83,7 @@

ByteCounterOutputStream byteCounter = new ByteCounterOutputStream(new NoOpOutputStream());

-        pkgIconService.writePkgIconImage(
+        pkgService.writePkgIconImage(
                 byteCounter,
                 context,
                 pkg.get(),
@@ -136,7 +136,7 @@
         response.setContentType(MediaType.PNG.toString());
response.setDateHeader(HttpHeaders.LAST_MODIFIED, pkg.get().getModifyTimestampSecondAccuracy().getTime());

-        pkgIconService.writePkgIconImage(
+        pkgService.writePkgIconImage(
                 response.getOutputStream(),
                 context,
                 pkg.get(),
@@ -181,7 +181,7 @@
         }

         try {
-            pkgIconService.storePkgIconImage(
+            pkgService.storePkgIconImage(
                     request.getInputStream(),
                     expectedSize,
                     context,
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/model/PkgSearchSpecification.java Wed Dec 11 08:25:33 2013 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/model/PkgSearchSpecification.java Sun Feb 2 09:15:35 2014 UTC
@@ -6,61 +6,18 @@
 package org.haikuos.haikudepotserver.pkg.model;

 import org.haikuos.haikudepotserver.dataobjects.Architecture;
+import org.haikuos.haikudepotserver.support.AbstractSearchSpecification;

 import java.util.Collection;

 /**
* <p>This model object specifies the parameters of a search into the system for packages. See the - * {@link org.haikuos.haikudepotserver.pkg.PkgSearchService} for further detail on this.</p> + * {@link org.haikuos.haikudepotserver.pkg.PkgService} for further detail on this.</p>
  */

-public class PkgSearchSpecification {
-
-    public enum ExpressionType {
-        CONTAINS
-    }
-
-    private String expression;
+public class PkgSearchSpecification extends AbstractSearchSpecification {

     private Collection<Architecture> architectures;
-
-    private ExpressionType expressionType;
-
-    private int offset;
-
-    private int limit;
-
-    public int getOffset() {
-        return offset;
-    }
-
-    public void setOffset(int offset) {
-        this.offset = offset;
-    }
-
-    public int getLimit() {
-        return limit;
-    }
-
-    public void setLimit(int value) {
-        this.limit = value;
-    }
-
-    public String getExpression() {
-        return expression;
-    }
-
-    public void setExpression(String expression) {
-        this.expression = expression;
-    }
-
-    public ExpressionType getExpressionType() {
-        return expressionType;
-    }
-
-    public void setExpressionType(ExpressionType expressionType) {
-        this.expressionType = expressionType;
-    }

     public Collection<Architecture> getArchitectures() {
         return architectures;
=======================================
--- /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml Fri Jan 31 10:26:48 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml Sun Feb 2 09:15:35 2014 UTC
@@ -57,6 +57,8 @@
<value>/js/app/directive/filesupplydirective.js</value> <value>/js/app/directive/validpassworddirective.js</value> <value>/js/app/directive/userlabeldirective.js</value> + <value>/js/app/directive/repositorylabeldirective.js</value> + <value>/js/app/directive/activeindicatordirective.js</value>

<value>/js/app/controller/viewpkgcontroller.js</value> <value>/js/app/controller/homecontroller.js</value>
@@ -68,6 +70,7 @@
<value>/js/app/controller/changepasswordcontroller.js</value> <value>/js/app/controller/morecontroller.js</value> <value>/js/app/controller/runtimeinformationcontroller.js</value> + <value>/js/app/controller/listrepositoriescontroller.js</value>

<value>/js/app/service/jsonrpcservice.js</value> <value>/js/app/service/messagesourceservice.js</value>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/css/haikudepotserver.css Thu Jan 16 08:37:44 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/css/haikudepotserver.css Sun Feb 2 09:15:35 2014 UTC
@@ -256,12 +256,17 @@

 /*
 ==================================
-TABLE
+ANGULARJS
 */

 [ng\:cloak], [ng-cloak], .ng-cloak {
     display: none;
 }
+
+/*
+==================================
+SPINNER
+*/

 /*
This material is for the spinner; it will fill the screen will white and then put a small animation on top for
@@ -284,4 +289,68 @@
     padding-top: 120px;
 }

+/*
+==================================
+SEARCH BAR
+*/

+#search-criteria-container {
+    background-color: rgb(253,207,49);
+    border: 1px solid black;
+}
+
+#search-criteria-container input {
+    font-size: 10px;
+}
+
+#search-criteria-container button {
+    font-size: 10px;
+    background-color: rgb(253,207,49);
+    border: 1px solid black;
+    border-radius: 3px;
+}
+
+#search-criteria-container button[disabled] {
+    border: 1px solid rgba(0, 0, 0, 0.30);
+}
+
+#search-criteria-container > div {
+    display: inline-block;
+    padding-top:2px;
+    padding-bottom:2px;
+    padding-left:4px;
+    padding-right:4px;
+}
+
+/*
+Within the search criteria to break-up the sections of the search criteria, this will put a dotted line
+between the sections.
+*/
+
+#search-criteria-container > div:nth-child(n+2) {
+    border-left: 1px dotted black;
+}
+
+#search-results-container {
+    margin-top: 8px;
+}
+
+/*
+==================================
+ACTIVE / INACTIVE INDICATORS
+*/
+
+.active-indicator {
+    background-color: grey;
+    width: 12px;
+    height: 12px;
+    border-radius: 6px;
+}
+
+.active-indicator.active-indicator-true {
+    background-color: darkolivegreen;
+}
+
+.inactive-indicator.active-indicator-false {
+    background-color: firebrick;
+}
=======================================
--- /haikudepotserver-webapp/src/main/webapp/css/home.css Mon Jan 13 10:50:43 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/css/home.css Sun Feb 2 09:15:35 2014 UTC
@@ -1,84 +1,12 @@
-
-#home-search-criteria-container {
-    background-color: rgb(253,207,49);
-    border: 1px solid black;
-}
-
-#home-search-criteria-container > div {
-    display: inline-block;
-    padding-top:2px;
-    padding-bottom:2px;
-    padding-left:4px;
-    padding-right:4px;
-}
-
-#home-search-criteria-container input {
-    font-size: 10px;
-}
-
-#home-search-criteria-container button {
-    font-size: 10px;
-    background-color: rgb(253,207,49);
-    border: 1px solid black;
-    border-radius: 3px;
-}
-
-#home-search-criteria-container button[disabled] {
-    border: 1px solid rgba(0, 0, 0, 0.30);
-}
-
-#home-search-criteria-architecture-container {
-    border-right: 1px dotted black;
-}
-
-#home-search-criteria-category-container {
-    border-right: 1px dotted black;
-}
-
-#home-search-criteria-predicates-container {
-}
-
-#home-search-criteria-bar-categories > span {
-    color: rgba(0, 0, 0, 0.50);
-}
-
-/*
-Special layout for the search field which is in the 3rd box
-*/
-
-#home-search-criteria-bar #home-search-criteria-bar-search {
-    padding-top: 4px;
-    padding-bottom: 4px;
-    padding-left: 8px;
-    padding-right: 8px;
-}
-
-#home-search-criteria-bar > div.selected {
-    background-color: white;
-    border-bottom: white 1px solid;
-}
-
-#home-search-criteria-bar > div:not(.selected) {
-    border-bottom: black 1px solid;
-}
-
-#home-search-criteria-bar > div:nth-child(n+2) {
-    border-left: black 1px solid;
-}
-
-#home-search-results-container {
-    margin-top: 8px;
-}
-
-#home-search-results-container > table > tbody > tr > td:nth-child(1) {
+#search-results-container > table > tbody > tr > td:nth-child(1) {
     width: 24px;
 }

-#home-search-results-table > tbody > tr > td:nth-child(1) {
+#search-results-container > table > tbody > tr > td:nth-child(1) {
     width:18px;
     text-align: center;
 }

-#home-search-results-table > tbody > tr > td:nth-child(2) {
+#search-results-container > table > tbody > tr > td:nth-child(2) {
     width:50%;
 }
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/constants.js Fri Jan 31 10:26:48 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/constants.js Sun Feb 2 09:15:35 2014 UTC
@@ -7,6 +7,7 @@

         ARCHITECTURE_CODE_DEFAULT : 'x86',

+        ENDPOINT_API_V1_REPOSITORY : '/api/v1/repository',
         ENDPOINT_API_V1_PKG : '/api/v1/pkg',
         ENDPOINT_API_V1_CAPTCHA : '/api/v1/captcha',
         ENDPOINT_API_V1_MISCELLANEOUS : '/api/v1/miscellaneous',
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/home.html Tue Jan 14 09:30:30 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/home.html Sun Feb 2 09:15:35 2014 UTC
@@ -1,34 +1,31 @@
-
 <div class="content-container">

-    <div id="home-search-criteria-container">
-        <div id="home-search-criteria-architecture-container">
+    <div id="search-criteria-container">
+        <div>
<select ng-model="selectedArchitecture" ng-options="anArchitecture.code for anArchitecture in architectures"></select>
         </div>
-        <div id="home-search-criteria-category-container">
+        <div>
<select ng-model="selectedViewCriteriaTypeOption" ng-options="aViewCriteriaTypeOptions.name for aViewCriteriaTypeOptions in viewCriteriaTypeOptions"></select>
         </div>
-        <div id="home-search-criteria-predicates-container">
-            <div ng-show="'SEARCH'==selectedViewCriteriaTypeOption.code">
-                <input
-                        ng-model="searchExpression"
-                        ui-keypress="{enter:'goSearch()'}"
-                        placeholder="Search..."></input>
-                <button
- ng-disabled="!searchExpression | | !searchExpression.length"
-                        ng-click="goSearch()">
-                    Go
-                </button>
-            </div>
- <div ng-show="'CATEGORIES'==selectedViewCriteriaTypeOption.code">
-                Not supported yet!
-            </div>
+        <div ng-show="'SEARCH'==selectedViewCriteriaTypeOption.code">
+            <input
+                    ng-model="searchExpression"
+                    ui-keypress="{enter:'goSearch()'}"
+                    placeholder="Search..."></input>
+            <button
+ ng-disabled="!searchExpression | | !searchExpression.length"
+                    ng-click="goSearch()">
+                Go
+            </button>
+        </div>
+        <div ng-show="'CATEGORIES'==selectedViewCriteriaTypeOption.code">
+            Not supported yet!
         </div>
     </div>

     <!-- RESULTS -->

-    <div id="home-search-results-container">
+    <div id="search-results-container">

         <div ng-show="pkgs && 0==pkgs.length" class="info-container">
             <strong>No results; </strong>
@@ -44,7 +41,7 @@
                 <span ng-show="!hasMore">&raquo;</span>
             </div>

-            <table id="home-search-results-table" class="table-general">
+            <table class="table-general">
                 <thead>
                 <th></th>
                 <th>Package</th>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/homecontroller.js Fri Jan 31 10:26:48 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/homecontroller.js Sun Feb 2 09:15:35 2014 UTC
@@ -189,9 +189,9 @@
                         }]
                     ).then(
                     function(result) {
-                        $scope.pkgs = result.pkgs;
+                        $scope.pkgs = result.items;
                         $scope.hasMore = result.hasMore;
-                        $log.info('found '+result.pkgs.length+' packages');
+ $log.info('found '+result.items.length+' packages');
                         amFetchingPkgs = false;
                     },
                     function(err) {
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/more.html Fri Jan 31 10:26:48 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/more.html Sun Feb 2 09:15:35 2014 UTC
@@ -14,6 +14,9 @@
         <li>
             <a href="" ng-click="goHome()">Home</a>
         </li>
+        <li>
+            <a href="" ng-click="goListRepositories()">Repositories</a>
+        </li>
         <li ng-show="canGoRuntimeInformation">
<a href="" ng-click="goRuntimeInformation()">Runtime information</a>
         </li>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/morecontroller.js Fri Jan 31 10:26:48 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/morecontroller.js Sun Feb 2 09:15:35 2014 UTC
@@ -57,6 +57,13 @@
             }

             refreshAuthorization();
+
+            // -------------------
+            // REPOSITORIES
+
+            $scope.goListRepositories = function() {
+                $location.path('/listrepositories').search({});
+            }

             // -------------------
             // RUNTIME INFORMATION
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/routes.js Fri Jan 31 10:33:41 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/routes.js Sun Feb 2 09:15:35 2014 UTC
@@ -8,6 +8,7 @@
         '$routeProvider',
         function($routeProvider) {
             $routeProvider
+ .when('/listrepositories',{controller:'ListRepositoriesController', templateUrl:'/js/app/controller/listrepositories.html'}) .when('/runtimeinformation',{controller:'RuntimeInformationController', templateUrl:'/js/app/controller/runtimeinformation.html'}) .when('/more',{controller:'MoreController', templateUrl:'/js/app/controller/more.html'}) .when('/authenticateuser',{controller:'AuthenticateUserController', templateUrl:'/js/app/controller/authenticateuser.html'})
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/service/breadcrumbsservice.js Fri Jan 31 10:26:48 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/service/breadcrumbsservice.js Sun Feb 2 09:15:35 2014 UTC
@@ -13,6 +13,13 @@

             var BreadcrumbsService = {

+                createListRepositories : function() {
+                    return {
+                        title : 'List Repositories',
+                        path : '/listrepositories'
+                    };
+                },
+
                 createRuntimeInformation : function() {
                     return {
                         title : 'Runtime Information',
=======================================
--- /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotsever/api1/PkgApiIT.java Sat Jan 18 09:59:17 2014 UTC +++ /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotsever/api1/PkgApiIT.java Sun Feb 2 09:15:35 2014 UTC
@@ -13,7 +13,7 @@
 import org.haikuos.haikudepotserver.api1.model.pkg.*;
 import org.haikuos.haikudepotserver.api1.support.ObjectNotFoundException;
 import org.haikuos.haikudepotserver.dataobjects.Pkg;
-import org.haikuos.haikudepotserver.pkg.PkgIconService;
+import org.haikuos.haikudepotserver.pkg.PkgService;
 import org.haikuos.haikudepotserver.support.Closeables;
 import org.haikuos.haikudepotsever.api1.support.AbstractIntegrationTest;
import org.haikuos.haikudepotsever.api1.support.IntegrationTestSupportService;
@@ -31,7 +31,7 @@
     PkgApi pkgApi;

     @Resource
-    PkgIconService pkgIconService;
+    PkgService pkgService;

     @Test
     public void searchPkgsTest() {
@@ -49,9 +49,9 @@
         // ------------------------------------

         Assertions.assertThat(result.hasMore).isTrue();
-        Assertions.assertThat(result.pkgs.size()).isEqualTo(2);
-        Assertions.assertThat(result.pkgs.get(0).name).isEqualTo("pkg1");
-        Assertions.assertThat(result.pkgs.get(1).name).isEqualTo("pkg2");
+        Assertions.assertThat(result.items.size()).isEqualTo(2);
+        Assertions.assertThat(result.items.get(0).name).isEqualTo("pkg1");
+        Assertions.assertThat(result.items.get(1).name).isEqualTo("pkg2");
     }

     @Test
@@ -123,7 +123,7 @@
throw new IllegalStateException("unable to find the test input stream for the icon image");
             }

-            pkgIconService.storePkgIconImage(
+            pkgService.storePkgIconImage(
                     sample32InputStream,
                     32,
                     integrationTestSupportService.getObjectContext(),
=======================================
--- /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotsever/api1/support/IntegrationTestSupportService.java Sat Jan 18 09:59:17 2014 UTC +++ /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotsever/api1/support/IntegrationTestSupportService.java Sun Feb 2 09:15:35 2014 UTC
@@ -7,10 +7,7 @@

 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.configuration.server.ServerRuntime;
-import org.haikuos.haikudepotserver.dataobjects.Architecture;
-import org.haikuos.haikudepotserver.dataobjects.Pkg;
-import org.haikuos.haikudepotserver.dataobjects.PkgVersion;
-import org.haikuos.haikudepotserver.dataobjects.Repository;
+import org.haikuos.haikudepotserver.dataobjects.*;
 import org.springframework.stereotype.Service;

 import javax.annotation.Resource;
@@ -107,6 +104,8 @@

     public static class StandardTestData {

+        public User rootUser;
+
         public Repository repository;

         public Pkg pkg1;

==============================================================================
Revision: 43ab6c1b9f0e
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Sun Feb  2 09:38:27 2014 UTC
Log:      + explain timestamps in api docs
+ small fixes

http://code.google.com/p/haiku-depot-web-app/source/detail?r=43ab6c1b9f0e

Modified:
 /haikudepotserver-docs/src/main/latex/docs/part-api.tex
 /haikudepotserver-webapp/src/main/webapp/css/home.css
/haikudepotserver-webapp/src/main/webapp/js/app/directive/repositorylabeldirective.js /haikudepotserver-webapp/src/main/webapp/js/app/service/errorhandlingservice.js /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotsever/api1/support/IntegrationTestSupportService.java

=======================================
--- /haikudepotserver-docs/src/main/latex/docs/part-api.tex Mon Jan 27 08:59:15 2014 UTC +++ /haikudepotserver-docs/src/main/latex/docs/part-api.tex Sun Feb 2 09:38:27 2014 UTC
@@ -25,6 +25,10 @@

Reference data means data in the application that is generally invariant. Examples include the mime types, natural language, url types and so on. In these cases, an API will generally provide for access to the list of reference data. The client is expected to obtain such reference data as necessary and cache reference data locally.

+\subsubsection{Date and Time Data}
+
+The system has only a concept of a {\it moment in time} which is called a timestamp. The timestamp is typically communicated as the number of milliseconds elapsed since the epoc represented as a 64bit integer. The timestamp communicated via the API is always relative to UTC.
+
 \subsubsection{Invocations and Transport}

The term ``invocation'' refers to a request-response cycle from the client software into the application server over the HTTP protocol. Each API invocation is made in a {\it stateless} manner in that each invocation is not dependent on the prior invocation.
=======================================
--- /haikudepotserver-webapp/src/main/webapp/css/home.css Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/css/home.css Sun Feb 2 09:38:27 2014 UTC
@@ -1,12 +1,16 @@
-#search-results-container > table > tbody > tr > td:nth-child(1) {
+/*
+This specific layout works off the premise that there is only one table on this page.
+*/
+
+.table-general > tbody > tr > td:nth-child(1) {
     width: 24px;
 }

-#search-results-container > table > tbody > tr > td:nth-child(1) {
+.table-general > tbody > tr > td:nth-child(1) {
     width:18px;
     text-align: center;
 }

-#search-results-container > table > tbody > tr > td:nth-child(2) {
+.table-general > tbody > tr > td:nth-child(2) {
     width:50%;
 }
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/directive/repositorylabeldirective.js Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/repositorylabeldirective.js Sun Feb 2 09:38:27 2014 UTC
@@ -4,7 +4,7 @@
  */

 /**
- * <p>This directive renders a small span which briefly describes the package.</p> + * <p>This directive renders a small span which briefly describes the repository.</p>
  */

 angular.module('haikudepotserver').directive('repositoryLabel',function() {
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/service/errorhandlingservice.js Fri Jan 31 10:33:41 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/service/errorhandlingservice.js Sun Feb 2 09:38:27 2014 UTC
@@ -4,7 +4,7 @@
  */

 /**
- * <p>This service is used to centralize the handling of errors; providing consistent handling and error feedback. + * <p>This service is used to centralize the handling of errors; providing consistent handling and error feedback.</p>
  */

 angular.module('haikudepotserver').factory('errorHandling',
=======================================
--- /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotsever/api1/support/IntegrationTestSupportService.java Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotsever/api1/support/IntegrationTestSupportService.java Sun Feb 2 09:38:27 2014 UTC
@@ -104,8 +104,6 @@

     public static class StandardTestData {

-        public User rootUser;
-
         public Repository repository;

         public Pkg pkg1;

Other related posts:

  • » [haiku-depot-web] [haiku-depot-web-app] 5 new revisions pushed by haiku.li...@xxxxxxxxx on 2014-02-02 09:38 GMT - haiku-depot-web-app