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

  • From: haiku-depot-web-app@xxxxxxxxxxxxxx
  • To: haiku-depot-web@xxxxxxxxxxxxx
  • Date: Tue, 18 Feb 2014 10:24:08 +0000

master moved from 79eb8a4e32f8 to 4ee4560ddc66

5 new revisions:

Revision: fb41a5edfbe3
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Tue Feb 18 00:02:17 2014 UTC
Log:      + fix for hpkr chunk reading where reads cross over chunks...
http://code.google.com/p/haiku-depot-web-app/source/detail?r=fb41a5edfbe3

Revision: d1c7e6ee2e39
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Tue Feb 18 00:17:42 2014 UTC
Log:      + modify routes to handle re-navigation to the view pkg page
http://code.google.com/p/haiku-depot-web-app/source/detail?r=d1c7e6ee2e39

Revision: 2ba6b572106c
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Tue Feb 18 09:54:04 2014 UTC
Log:      + additional data for screenshots...
http://code.google.com/p/haiku-depot-web-app/source/detail?r=2ba6b572106c

Revision: 99da10a910a9
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Tue Feb 18 10:09:54 2014 UTC
Log:      + documentation update...
http://code.google.com/p/haiku-depot-web-app/source/detail?r=99da10a910a9

Revision: 4ee4560ddc66
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Tue Feb 18 10:16:48 2014 UTC
Log:      + cache control on icons and screenshots
http://code.google.com/p/haiku-depot-web-app/source/detail?r=4ee4560ddc66

==============================================================================
Revision: fb41a5edfbe3
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Tue Feb 18 00:02:17 2014 UTC
Log:      + fix for hpkr chunk reading where reads cross over chunks
+ implemented basic screenshot handling functions

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

Added:
 /haikudepotserver-webapp/src/main/webapp/css/editpkgscreenshots.css
/haikudepotserver-webapp/src/main/webapp/js/app/controller/editpkgscreenshots.html /haikudepotserver-webapp/src/main/webapp/js/app/controller/editpkgscreenshotscontroller.js /haikudepotserver-webapp/src/main/webapp/js/app/service/pkgscreenshotservice.js
Modified:
/haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/HpkStringTable.java /haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/heap/HpkHeapReader.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/PkgApiImpl.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/PkgService.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/controller/PkgScreenshotController.java
 /haikudepotserver-webapp/src/main/resources/messages.properties
 /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml
 /haikudepotserver-webapp/src/main/webapp/css/haikudepotserver.css
 /haikudepotserver-webapp/src/main/webapp/css/viewpkg.css
/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/viewpkg.html
/haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkgcontroller.js /haikudepotserver-webapp/src/main/webapp/js/app/directive/breadcrumbsdirective.js /haikudepotserver-webapp/src/main/webapp/js/app/directive/validpassworddirective.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/pkgiconservice.js
 /haikudepotserver-webapp/src/main/webapp/js/app/service/userstateservice.js

=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/css/editpkgscreenshots.css Tue Feb 18 00:02:17 2014 UTC
@@ -0,0 +1,24 @@
+#pkg-screenshots-list {
+    margin-top: 8px;
+    margin-bottom: 2px;
+}
+
+#pkg-screenshots-list > div {
+    margin-top: 2px;
+    margin-bottom: 2px;
+    background-color: #EEE;
+}
+
+#pkg-screenshots-list > div > img {
+    float: left;
+    border: 1px solid black;
+}
+
+#pkg-screenshots-list div.pkg-screenshot-controls {
+    margin-left: 190px;
+    padding-top: 4px;
+    padding-bottom: 4px;
+}
+
+
+
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/editpkgscreenshots.html Tue Feb 18 00:02:17 2014 UTC
@@ -0,0 +1,39 @@
+<breadcrumbs items="breadcrumbItems"></breadcrumbs>
+
+<div class="content-container edit-pkg-screenshots">
+
+    <div class="onelineform-container">
+        <form name="addPkgScreenshotForm" novalidate="novalidate">
+            PNG Image File :
+ <input name="file" type="file" ng-model="addPkgScreenshot.file" file-supply required></input>
+            <button
+                    ng-disabled="addPkgScreenshotForm.$invalid"
+                    ng-click="goAddPkgScreenshot()"
+                    type="submit"
+                    class="main-action">Add</button>
+
+ <error-messages key-prefix="addPkgScreenshot.file" error="addPkgScreenshotForm.file.$error" ng-show="!addPkgScreenshotForm.$pristine"></error-messages>
+
+        </form>
+    </div>
+
+    <div id="pkg-screenshots-list">
+        <div ng-repeat="pkgScreenshot in pkgScreenshots">
+            <img ng-src="{{pkgScreenshot.imageThumbnailUrl}}"></img>
+            <div class="pkg-screenshot-controls">
+                <div><strong>#{{$index+1}}</strong></div>
+
+                <ul>
+ <li><a href="" ng-click="goRemovePkgScreenshot(pkgScreenshot)">Remove</a></li> + <li><a href="{{pkgScreenshot.imageDownloadUrl}}">Download</a></li>
+                </ul>
+            </div>
+            <div style="clear: both;"></div>
+        </div>
+    </div>
+
+</div>
+
+<div class="footer"></div>
+<spinner spin="shouldSpin()"></spinner>
+
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/editpkgscreenshotscontroller.js Tue Feb 18 00:02:17 2014 UTC
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+angular.module('haikudepotserver').controller(
+    'EditPkgScreenshotsController',
+    [
+        '$scope','$log','$location','$routeParams',
+        'jsonRpc','constants','pkgScreenshot','errorHandling',
+        'breadcrumbs',
+        function(
+            $scope,$log,$location,$routeParams,
+            jsonRpc,constants,pkgScreenshot,errorHandling,
+            breadcrumbs) {
+
+ // the upload size must be less than this or it is too big for the
+            // far end to process.
+
+            var FILESIZEMAX = 2 * 1024 * 1024;
+
+            var THUMBNAIL_TARGETWIDTH = 180;
+            var THUMBNAIL_TARGETHEIGHT = 90;
+
+            $scope.breadcrumbItems = undefined;
+            $scope.pkg = undefined;
+            $scope.pkgScreenshots = undefined;
+            $scope.amCommunicating = false;
+            $scope.addPkgScreenshot = {
+                file : undefined
+            };
+
+            $scope.shouldSpin = function() {
+ return undefined == $scope.pkg || undefined == $scope.pkgScreenshots || $scope.amCommunicating;
+            }
+
+            $scope.deriveFormControlsContainerClasses = function(name) {
+ return $scope.addPkgScreenshotForm[name].$invalid ? ['form-control-group-error'] : [];
+            }
+
+ // pulls back the list of package screenshots so that they can be displayed
+            // in a list.
+
+            function refetchPkgScreenshots() {
+                jsonRpc.call(
+                        constants.ENDPOINT_API_V1_PKG,
+                        "getPkgScreenshots",
+                        [{ pkgName: $routeParams.name }]
+                    ).then(
+                    function(result) {
+ $scope.pkgScreenshots = _.map(result.items, function(item) {
+                            return {
+                                code : item.code,
+ imageThumbnailUrl : pkgScreenshot.url($scope.pkg,item.code,THUMBNAIL_TARGETWIDTH,THUMBNAIL_TARGETHEIGHT), + imageDownloadUrl : pkgScreenshot.rawUrl($scope.pkg,item.code)
+                            };
+                        })
+
+ $log.info('found '+result.items.length+' screenshots for pkg '+result.name);
+                    },
+                    function(err) {
+                        errorHandling.handleJsonRpcError(err);
+                    }
+                );
+            }
+
+ // pulls the pkg data back from the server so that it can be used to
+            // display the form.
+
+            function refetchPkg() {
+                jsonRpc.call(
+                        constants.ENDPOINT_API_V1_PKG,
+                        "getPkg",
+                        [{
+                            name: $routeParams.name,
+                            versionType: 'NONE',
+ architectureCode: undefined // not required if we don't need the version
+                        }]
+                    ).then(
+                    function(result) {
+                        $scope.pkg = result;
+                        $log.info('found '+result.name+' pkg');
+                        refreshBreadcrumbItems();
+                        refetchPkgScreenshots();
+                    },
+                    function(err) {
+                        errorHandling.handleJsonRpcError(err);
+                    }
+                );
+            }
+
+            refetchPkg();
+
+            function refreshBreadcrumbItems() {
+                $scope.breadcrumbItems = [
+ breadcrumbs.createViewPkg($scope.pkg,'latest',$location.search()['arch']),
+                    breadcrumbs.createEditPkgScreenshots($scope.pkg)
+                ];
+            }
+
+            // -------------------------
+            // ADDING A SCREENSHOT
+
+ // This function will check to make sure that the file is not too large or too small to be a valid PNG. + // The 'model' is an instance of ngModel from in the form onto which the invalidity can be applied. + // This will also reset any validation problems that relate to the bad format of the PNG payload from + // the server because the user will not know if an updated file is also bad until the server has seen it; + // ie: the validation is happening server-side rather than client-side.
+
+            $scope.$watch('addPkgScreenshot.file', function(newValue) {
+                var file = $scope.addPkgScreenshot.file;
+                var model = $scope.addPkgScreenshotForm['file']
+                model.$setValidity('badformatorsize',true);
+ model.$setValidity('badsize',undefined==file || (file.size
24 && file.size < FILESIZEMAX));
+            });
+
+ // This function will take the data from the form and will create the user from this data.
+
+            $scope.goAddPkgScreenshot = function() {
+
+                if($scope.addPkgScreenshotForm.$invalid) {
+ throw 'expected the editing of package screenshots only to be possible if the form is valid';
+                }
+
+                $scope.amCommunicating = true;
+
+ // two PUT requests are made to the server in order to convey the PNG data.
+
+ pkgScreenshot.addScreenshot($scope.pkg, $scope.addPkgScreenshot.file).then(
+                    function(code) {
+ $log.info('have added a screenshot for the pkg '+$scope.pkg.name);
+                        $scope.addPkgScreenshot.file = undefined;
+                        $scope.addPkgScreenshotForm.$setPristine();
+                        $scope.pkgScreenshots.push({
+                            code : code,
+ imageThumbnailUrl : pkgScreenshot.url($scope.pkg,code,THUMBNAIL_TARGETWIDTH,THUMBNAIL_TARGETHEIGHT), + imageDownloadUrl : pkgScreenshot.rawUrl($scope.pkg,code)
+                        })
+                    },
+                    function(e) {
+ if(e==pkgScreenshot.errorCodes.BADFORMATORSIZEERROR) { + $scope.addPkgScreenshotForm['file'].$setValidity('badformatorsize',false);
+                        }
+                        else {
+ $log.error('unable to add the screenshot for; '+$scope.pkg.name);
+                            $location.path('/error').search({});
+                        }
+                    }
+                )['finally'](
+                    function() {
+                        $scope.amCommunicating = false;
+
+                    }
+                )
+            }
+
+            // -------------------------
+            // REMOVE A SCREENSHOT
+
+            $scope.goRemovePkgScreenshot = function(pkgScreenshot) {
+                jsonRpc.call(
+                        constants.ENDPOINT_API_V1_PKG,
+                        "removePkgScreenshot",
+                        [{ code: pkgScreenshot.code }]
+                    ).then(
+                    function() {
+ $log.info('did remove screenshot '+pkgScreenshot.code); + $scope.pkgScreenshots = _.reject($scope.pkgScreenshots, function(item) {
+                            return item.code == pkgScreenshot.code;
+                        })
+                    },
+                    function(err) {
+                        errorHandling.handleJsonRpcError(err);
+                    }
+                );
+            }
+
+        }
+    ]
+);
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/service/pkgscreenshotservice.js Tue Feb 18 00:02:17 2014 UTC
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+/**
+ * <p>This service provides functionality for accessing and updating the screenshots for a package.</p>
+ */
+
+angular.module('haikudepotserver').factory('pkgScreenshot',
+    [
+        '$log','$q','$http',
+        function($log,$q,$http) {
+
+            var PkgScreenshot = {
+
+ // these are errors that may be returned to the caller below. They match to the HTTP status codes
+                // used, but this should not be relied upon.
+
+                errorCodes : {
+                    BADFORMATORSIZEERROR : 415,
+                    NOTFOUND : 404,
+                    BADREQUEST : 400,
+                    UNKNOWN : -1
+                },
+
+                /**
+ * <p>This is a map of HTTP headers that is sent on each request into the server.</p>
+                 */
+
+                headers : {},
+
+                /**
+ * <p>This method will set the HTTP header that is sent on each request. This is handy,
+                 * for example for authentication.</p>
+                 */
+
+                setHeader : function(name, value) {
+
+                    if(!name || 0==''+name.length) {
+                        throw 'the name of the http header is required';
+                    }
+
+                    if(!value || 0==''+value.length) {
+                        delete PkgScreenshot.headers[name];
+                    }
+                    else {
+                        PkgScreenshot.headers[name] = value;
+                    }
+
+                },
+
+ // this function will add a screenshot for the package nominated.
+
+                addScreenshot : function(pkg, screenshotFile) {
+
+                    if(!pkg) {
+ throw 'the pkg must be supplied to add a pkg screenshot';
+                    }
+
+                    if(!screenshotFile) {
+ throw 'to add a screenshot for '+pkg.name+' the image file must be provided';
+                    }
+
+                    var deferred = $q.defer();
+
+                    $http({
+                        cache: false,
+                        method: 'PUT',
+                        url: '/pkgscreenshot/'+pkg.name+'.png',
+                        headers: _.extend(
+                            { 'Content-Type' : 'image/png' },
+                            PkgScreenshot.headers),
+                        data: screenshotFile
+                    })
+                        .success(function(data,status,header,config) {
+ var code = header('X-HaikuDepotServer-ScreenshotCode');
+
+                            if(!code || !code.length) {
+ throw 'the screenshot code should have been supplied back from creating a new screenshot';
+                            }
+
+                            deferred.resolve(code);
+                        })
+                        .error(function(data,status,header,config) {
+                            switch(status) {
+                                case 200:
+                                    deferred.resolve();
+                                    break;
+
+                                case 415: // unsupported media type
+ deferred.reject(PkgScreenshot.errorCodes.BADFORMATORSIZEERROR);
+                                    break;
+
+                                case 400: // bad request
+ deferred.reject(PkgScreenshot.errorCodes.BADREQUEST);
+                                    break;
+
+                                case 404: // not found
+ deferred.reject(PkgScreenshot.errorCodes.NOTFOUND);
+                                    break;
+
+                                default:
+ deferred.reject(PkgScreenshot.errorCodes.UNKNOWN);
+                                    break;
+
+                            }
+                        });
+
+                    return deferred.promise;
+                },
+
+                /**
+ * <p>This function will provide a raw-data download for the screenshot.</p>
+                 */
+
+                rawUrl : function(pkg, code) {
+                    if(!pkg) {
+ throw 'the pkg must be supplied to get a package screenshot url';
+                    }
+
+                    if(!code || !code.length) {
+ throw 'the code must be supplied to derive a url for the screenshot image';
+                    }
+
+                    return '/pkgscreenshot/raw/' + code;
+                },
+
+                /**
+ * <p>This function will provide a URL to the packages' screenshot. The package object is supplied and + * a code is supplied to identify the actual screenshot. The target width and height provide the + * size o the image that the screenshot will be scaled to.</p>
+                 */
+
+                url : function(pkg, code, targetWidth, targetHeight) {
+                    if(!pkg) {
+ throw 'the pkg must be supplied to get a package screenshot url';
+                    }
+
+                    if(!code || !code.length) {
+ throw 'the code must be supplied to derive a url for the screenshot image';
+                    }
+
+                    var u = '/pkgscreenshot/' + code + '.png';
+                    var q = [];
+
+                    if(pkg.modifyTimestamp) {
+                        q.push('m=' + pkg.modifyTimestamp);
+                    }
+
+                    if(targetWidth) {
+                        q.push('tw=' + targetWidth);
+                    }
+
+                    if(targetHeight) {
+                        q.push('th=' + targetHeight);
+                    }
+
+                    if(q.length) {
+                        u += '?' + q.join('&');
+                    }
+
+                    return u;
+                }
+
+            };
+
+            return PkgScreenshot;
+
+        }
+    ]
+);
=======================================
--- /haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/HpkStringTable.java Fri Nov 15 08:51:45 2013 UTC +++ /haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/HpkStringTable.java Tue Feb 18 00:02:17 2014 UTC
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013, Andrew Lindesay
+ * Copyright 2013-2014, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

@@ -47,7 +47,7 @@

     }

- // TODO; could avoid the big read into a buffer by reading the heap byte by byte. + // TODO; could avoid the big read into a buffer by reading the heap byte by byte or with a buffer.
     private String[] readStrings() throws HpkException {
         String[] result = new String[(int) expectedCount];
         byte[] stringsDataBuffer = new byte[(int) heapLength];
@@ -58,6 +58,9 @@
                 new HeapCoordinates(
                         heapOffset,
                         heapLength));
+
+//        try { Files.write(stringsDataBuffer, new File("/tmp/dat")); }
+//        catch(IOException ioe) {}

         // now work through the data and load them into the strings.

@@ -73,10 +76,14 @@

                 return result;
             }
+
+            if(stringIndex >= expectedCount) {
+ throw new HpkException("have already read all of the strings from the string table, but have not exhausted the string table data");
+            }

             int start = offset;

- while(0!=stringsDataBuffer[offset] && offset < stringsDataBuffer.length) { + while(offset < stringsDataBuffer.length && 0!=stringsDataBuffer[offset]) {
                 offset++;
             }

@@ -88,7 +95,7 @@

         }

- throw new HpkException("expected to find the null-terminator for the list of strings, but was not able to find one."); + throw new HpkException("expected to find the null-terminator for the list of strings, but was not able to find one; did read "+stringIndex+" of "+expectedCount);
     }

     private String[] getStrings() throws HpkException {
=======================================
--- /haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/heap/HpkHeapReader.java Fri Nov 15 08:51:45 2013 UTC +++ /haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/heap/HpkHeapReader.java Tue Feb 18 00:02:17 2014 UTC
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013, Andrew Lindesay
+ * Copyright 2013-2014, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

@@ -338,7 +338,7 @@
                     buffer,
                     bufferOffset + chunkLength,
                     new HeapCoordinates(
-                            heapOffset + chunkLength,
+                            coordinates.getOffset() + chunkLength,
                             coordinates.getLength() - chunkLength));
         }

=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/PkgApiImpl.java Wed Feb 12 10:07:08 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/PkgApiImpl.java Tue Feb 18 00:02:17 2014 UTC
@@ -284,7 +284,7 @@
     @Override
public GetPkgScreenshotsResult getPkgScreenshots(GetPkgScreenshotsRequest getPkgScreenshotsRequest) throws ObjectNotFoundException {
         Preconditions.checkNotNull(getPkgScreenshotsRequest);
-        Preconditions.checkNotNull(getPkgScreenshotsRequest.pkgName);
+ Preconditions.checkState(!Strings.isNullOrEmpty(getPkgScreenshotsRequest.pkgName));

         final ObjectContext context = serverRuntime.getContext();
Optional<Pkg> pkgOptional = Pkg.getByName(context, getPkgScreenshotsRequest.pkgName);
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/PkgService.java Fri Feb 14 08:42:33 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/PkgService.java Tue Feb 18 00:02:17 2014 UTC
@@ -248,6 +248,11 @@
         byte[] pngData = ByteStreams.toByteArray(input);
         ImageHelper.Size size =  imageHelper.derivePngSize(pngData);

+        if(null==size) {
+ logger.warn("attempt to set the package icon for package {}, but the data does not look like png",pkg.getName());
+            throw new BadPkgIconException();
+        }
+
// 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.

@@ -322,7 +327,7 @@
ByteArrayInputStream imageInputStream = new ByteArrayInputStream(data);
             BufferedImage bufferedImage = ImageIO.read(imageInputStream);
BufferedImage scaledBufferedImage = Scalr.resize(bufferedImage, targetWidth, targetHeight);
-            ImageIO.write(scaledBufferedImage, "PNG", output);
+            ImageIO.write(scaledBufferedImage, "png", output);
         }
         else {
             output.write(data);
@@ -347,11 +352,17 @@
         byte[] pngData = ByteStreams.toByteArray(input);
         ImageHelper.Size size =  imageHelper.derivePngSize(pngData);

+        if(null==size) {
+ logger.warn("attempt to store a screenshot image that is not a png");
+            throw new BadPkgScreenshotException();
+        }
+
// check that the file roughly looks like PNG and the size is something
         // reasonable.

if(size.height > SCREENSHOT_SIDE_LIMIT || size.width > SCREENSHOT_SIDE_LIMIT) { logger.warn("attempt to store a screenshot image that is too large; "+size.toString());
+            throw new BadPkgScreenshotException();
         }

MediaType png = MediaType.getByCode(context, com.google.common.net.MediaType.PNG.toString()).get();
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/controller/PkgScreenshotController.java Wed Feb 12 10:07:08 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/controller/PkgScreenshotController.java Tue Feb 18 00:02:17 2014 UTC
@@ -62,8 +62,8 @@
     public void fetchHead(
             HttpServletRequest request,
             HttpServletResponse response,
-            @RequestParam(value = KEY_TARGETWIDTH) Integer targetWidth,
-            @RequestParam(value = KEY_TARGETHEIGHT) Integer targetHeight,
+ @RequestParam(value = KEY_TARGETWIDTH, required=true) Integer targetWidth, + @RequestParam(value = KEY_TARGETHEIGHT, required=true) Integer targetHeight,
             @PathVariable(value = KEY_FORMAT) String format,
@PathVariable(value = KEY_SCREENSHOTCODE) String screenshotCode)
             throws IOException {
@@ -149,9 +149,6 @@
             throw new ScreenshotNotFound();
         }

- ByteCounterOutputStream byteCounter = new ByteCounterOutputStream(new NoOpOutputStream());
-
- response.setHeader(HttpHeaders.CONTENT_LENGTH, Long.toString(byteCounter.getCounter()));
         response.setContentType(MediaType.PNG.toString());
         response.setDateHeader(
                 HttpHeaders.LAST_MODIFIED,
@@ -164,6 +161,55 @@
                 targetWidth,
                 targetHeight);
     }
+
+    /**
+ * <p>This one downloads the raw data that is stored for a screenshot.</p>
+     */
+
+ @RequestMapping(value = "/raw/{"+KEY_SCREENSHOTCODE+"}", method = RequestMethod.GET)
+    public void fetchRawGet(
+            HttpServletRequest request,
+            HttpServletResponse response,
+ @PathVariable(value = KEY_SCREENSHOTCODE) String screenshotCode)
+            throws IOException {
+
+        if(Strings.isNullOrEmpty(screenshotCode)) {
+            throw new MissingScreenshotCode();
+        }
+
+        ObjectContext context = serverRuntime.getContext();
+ Optional<PkgScreenshot> screenshotOptional = PkgScreenshot.getByCode(context, screenshotCode);
+
+        if(!screenshotOptional.isPresent()) {
+            throw new ScreenshotNotFound();
+        }
+
+ byte[] data = screenshotOptional.get().getPkgScreenshotImage().get().getData(); + org.haikuos.haikudepotserver.dataobjects.MediaType mediaType = screenshotOptional.get().getPkgScreenshotImage().get().getMediaType();
+
+        // TODO - find a better way to do this.
+        String extension = null;
+
+        if(mediaType.getCode().equals(MediaType.PNG.toString())) {
+            extension = "png";
+        }
+
+        if(null==extension) {
+ throw new IllegalStateException("the media type for the screenshot is not able to be converted into a file extension");
+        }
+
+        response.setContentLength(data.length);
+        response.setContentType(mediaType.getCode());
+        response.setHeader(
+                HttpHeaders.CONTENT_DISPOSITION,
+                String.format(
+                        "attachment; filename=\"%s__%s.%s\"",
+                        screenshotOptional.get().getPkg().getName(),
+                        screenshotOptional.get().getCode(),
+                        extension));
+
+        response.getOutputStream().write(data);
+    }

     /**
* <p>This handler will take-up an HTTP PUT that provides a new screenshot for the package.</p>
=======================================
--- /haikudepotserver-webapp/src/main/resources/messages.properties Sun Feb 9 10:38:52 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/messages.properties Tue Feb 18 00:02:17 2014 UTC
@@ -17,6 +17,7 @@
changePassword.newPasswordClearRepeated.repeat=The password has not been repeated correctly. changePassword.captchaResponse.required=The result of this simple question must be supplied to prove that the change password is from a human operator. changePassword.oldPasswordClear.mismatched=Authentication problem; try to enter the existing password again. +changePassword.captchaResponse.badresponse=The response supplied does not match the question in the image or the question has expired; a new image has been provided.

 authenticateUser.nickname.required=The nickname is required to login.
 authenticateUser.passwordClear.required=The password is required to login.
@@ -45,6 +46,9 @@
 addEditRepository.url.required=The url to the .hpkr data must be supplied.
 addEditRepository.url.malformed=The url must be well-formed.

+addPkgScreenshot.file.badformatorsize=The file must be a PNG image no larger than 1500x1500 and smaller than 2 megabytes in length.
+addPkgScreenshot.file.required=A data file is required to add a screenshot.
+addPkgScreenshot.file.badsize=The file must be a PNG image no larger than 1500x1500 and smaller than 2 megabytes in length.
 # Test case

 test.it=Test line for integration testing
=======================================
--- /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml Sun Feb 9 10:00:28 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml Tue Feb 18 00:02:17 2014 UTC
@@ -77,12 +77,14 @@
<value>/js/app/controller/listrepositoriescontroller.js</value> <value>/js/app/controller/viewrepositorycontroller.js</value> <value>/js/app/controller/addeditrepositorycontroller.js</value> + <value>/js/app/controller/editpkgscreenshotscontroller.js</value>

<value>/js/app/service/jsonrpcservice.js</value> <value>/js/app/service/messagesourceservice.js</value> <value>/js/app/service/userstateservice.js</value> <value>/js/app/service/referencedataservice.js</value> <value>/js/app/service/pkgiconservice.js</value> + <value>/js/app/service/pkgscreenshotservice.js</value> <value>/js/app/service/breadcrumbsservice.js</value> <value>/js/app/service/errorhandlingservice.js</value>

@@ -109,6 +111,7 @@
                             <value>/css/viewpkg.css</value>
                             <value>/css/banner.css</value>
                             <value>/css/breadcrumbs.css</value>
+                            <value>/css/editpkgscreenshots.css</value>
                         </list>
                     </property>
                 </bean>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/css/haikudepotserver.css Sat Feb 8 11:31:22 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/css/haikudepotserver.css Tue Feb 18 00:02:17 2014 UTC
@@ -64,7 +64,6 @@
 ALERT BOXES FOR USE OUTSIDE OF FORMS
 */

-
 .alert-container {
     padding: 8px;
     border: 1px solid red;
@@ -78,6 +77,17 @@
     background-color: white;
     color: black;
 }
+
+.onelineform-container {
+    padding: 8px;
+    border: 1px solid black;
+    background-color: white;
+    color: black;
+}
+
+.onelineform-container form {
+    margin-bottom: 0;
+}

 /*
 ==================================
=======================================
--- /haikudepotserver-webapp/src/main/webapp/css/viewpkg.css Mon Jan 13 10:50:43 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/css/viewpkg.css Tue Feb 18 00:02:17 2014 UTC
@@ -23,7 +23,7 @@
     float: right;
     width: 480px;
     border: 1px solid black;
-    min-height: 320px;
+    min-height: 96px;
     position: relative;
     background-color: #EEE;
 }
@@ -32,6 +32,11 @@
     background-color: #444;
     color: white;
     padding: 4px;
+    border-bottom: 1px solid black;
+}
+
+#pkg-screenshot-title.muted {
+    background-color: rgba(1,1,1,0.5);
 }

 /*
@@ -39,8 +44,8 @@
 for this package.
 */

-#pkg-screenshot-none {
-    margin-top: 32px;
+#pkg-screenshot-status {
+    margin-top: 20px;
     margin-bottom: 0px;
     width: 240px;
     margin-left: auto;
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/editpkgiconcontroller.js Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/editpkgiconcontroller.js Tue Feb 18 00:02:17 2014 UTC
@@ -8,9 +8,11 @@
     [
         '$scope','$log','$location','$routeParams',
         'jsonRpc','constants','pkgIcon','errorHandling',
+        'breadcrumbs',
         function(
             $scope,$log,$location,$routeParams,
-            jsonRpc,constants,pkgIcon,errorHandling) {
+            jsonRpc,constants,pkgIcon,errorHandling,
+            breadcrumbs) {

             $scope.breadcrumbItems = undefined;
             $scope.pkg = undefined;
@@ -56,14 +58,9 @@

             function refreshBreadcrumbItems() {
                 $scope.breadcrumbItems = [
-                    {
-                        title : $scope.pkg.name,
-                        path : '/viewpkg/'+$scope.pkg.name
-                    },
-                    {
-                        title : 'Edit Icon', // TODO - localize
-                        path : '/editpkgicon/'+$scope.pkg.name
-                    }];
+ breadcrumbs.createViewPkg($scope.pkg,'latest',$location.search()['arch']),
+                    breadcrumbs.createEditPkgIcon($scope.pkg)
+                ];
             }

// This function will check to make sure that the file is not too large or too small to be a valid PNG.
@@ -93,12 +90,7 @@
                 icon16FileDidChange();
             });

-
-            $scope.nicknameDidChange = function() {
- $scope.createUserForm.nickname.$setValidity('notunique',true);
-            }
-
- // This function will take the data from the form and will create the user from this data. + // This function will take the data from the form and load in the new pkg icons

             $scope.goStorePkgIcons = function() {

=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/homecontroller.js Sun Feb 2 09:15:35 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/homecontroller.js Tue Feb 18 00:02:17 2014 UTC
@@ -139,14 +139,6 @@

                 return false;
             }
-
-            $scope.classPreviousPage = function() {
-                return $scope.offset > 0 ? [] : ['disabled'];
-            }
-
-            $scope.classNextPage = function() {
-                return $scope.hasMore ? [] : ['disabled'];
-            }

             // ---- VIEW PKG + VERSION

=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkg.html Sat Feb 8 11:31:22 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkg.html Tue Feb 18 00:02:17 2014 UTC
@@ -26,16 +26,28 @@
         <div id="pkg-screenshot-container">

             <div id="pkg-screenshot-pagination-container">
- <pagination-arrow active="false" direction="left"></pagination-arrow> - <pagination-arrow active="false" direction="right"></pagination-arrow>
+                <pagination-arrow
+                        page-click="goPreviousScreenshot()"
+ active="pkgScreenshots.length && pkgScreenshotOffset > 0"
+                        direction="left"></pagination-arrow>
+                <pagination-arrow
+                        page-click="goNextScreenshot()"
+ active="pkgScreenshots.length && pkgScreenshotOffset < (pkgScreenshots.length-1)"
+                        direction="right"></pagination-arrow>
             </div>

             <div id="pkg-screenshot-title">
-                Screenshots
+                <span ng-show="!pkgScreenshots.length">Screenshots</span>
+                <span ng-show="pkgScreenshots.length">
+ Screenshot {{pkgScreenshotOffset+1}} <span class="muted">of {{pkgScreenshots.length}}</span>
+                </span>
             </div>

-            <div id="pkg-screenshot-none">
-                No screenshots available
+ <img ng-show="pkgScreenshots.length" ng-src="{{currentPkgScreenshot().imageThumbnailUrl}}">
+
+ <div id="pkg-screenshot-status" ng-show="!pkgScreenshots.length">
+                <span ng-show="undefined==pkgScreenshots">Loading...</span>
+ <span ng-show="pkgScreenshots && !pkgScreenshots.length">No screenshots available</span>
             </div>
         </div>

@@ -51,6 +63,9 @@
<li ng-show="canEditIcon()" pkg="pkg" show-if-pkg-permission="'PKG_EDITICON'">
                     <a href="" ng-click="goEditIcon()">Edit icons</a>
                 </li>
+ <li ng-show="canEditScreenshots()" pkg="pkg" show-if-pkg-permission="'PKG_EDITSCREENSHOT'"> + <a href="" ng-click="goEditScreenshots()">Edit screenshots</a>
+                </li>
             </ul>
         </div>

=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkgcontroller.js Wed Feb 12 10:07:08 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkgcontroller.js Tue Feb 18 00:02:17 2014 UTC
@@ -8,12 +8,19 @@
     [
         '$scope','$log','$location','$routeParams',
         'jsonRpc','constants','userState','errorHandling',
+        'pkgScreenshot',
         function(
             $scope,$log,$location,$routeParams,
-            jsonRpc,constants,userState,errorHandling) {
+            jsonRpc,constants,userState,errorHandling,
+            pkgScreenshot) {
+
+            var SCREENSHOT_THUMBNAIL_TARGETWIDTH = 480;
+            var SCREENSHOT_THUMBNAIL_TARGETHEIGHT = 320;

             $scope.breadcrumbItems = undefined;
             $scope.pkg = undefined;
+            $scope.pkgScreenshots = undefined;
+            $scope.pkgScreenshotOffset = 0;

             refetchPkg();

@@ -28,6 +35,10 @@
             $scope.canEditIcon = function() {
                 return $scope.pkg;
             }
+
+            $scope.canEditScreenshots = function() {
+                return $scope.pkg;
+            }

             $scope.homePageLink = function() {
                 var u = undefined;
@@ -67,16 +78,81 @@
                         $scope.pkg = result;
                         $log.info('found '+result.name+' pkg');
                         refreshBreadcrumbItems();
+                        refetchPkgScreenshots();
                     },
                     function(err) {
                         errorHandling.handleJsonRpcError(err);
                     }
                 );
             }
+
+            // ------------------------
+            // SCREENSHOTS
+
+            $scope.goPreviousScreenshot = function() {
+ if($scope.pkgScreenshots && $scope.pkgScreenshotOffset > 0) {
+                    $scope.pkgScreenshotOffset--;
+                }
+                return false;
+            }
+
+            $scope.goNextScreenshot = function() {
+ if($scope.pkgScreenshots && $scope.pkgScreenshotOffset < ($scope.pkgScreenshots.length-1)) {
+                    $scope.pkgScreenshotOffset++;
+                }
+                return false;
+            }
+
+            $scope.currentPkgScreenshot = function() {
+ return $scope.pkgScreenshots ? $scope.pkgScreenshots[$scope.pkgScreenshotOffset] : null;
+            }
+
+            function refetchPkgScreenshots() {
+
+                $scope.pkgScreenshots = undefined;
+
+                jsonRpc.call(
+                        constants.ENDPOINT_API_V1_PKG,
+                        "getPkgScreenshots",
+                        [{ pkgName: $scope.pkg.name }]
+                    ).then(
+                    function(result) {
+ $scope.pkgScreenshots = _.map(result.items, function(item) {
+                            return {
+                                code : item.code,
+                                imageThumbnailUrl : pkgScreenshot.url(
+                                    $scope.pkg,
+                                    item.code,
+                                    SCREENSHOT_THUMBNAIL_TARGETWIDTH,
+                                    SCREENSHOT_THUMBNAIL_TARGETHEIGHT),
+                                imageDownloadUrl : pkgScreenshot.rawUrl(
+                                    $scope.pkg,
+                                    item.code)
+                            };
+                        });
+
+                        $scope.pkgScreenshotOffset = 0;
+
+ $log.info('found '+result.items.length+' screenshots for pkg '+$routeParams.name);
+                        refreshBreadcrumbItems();
+                    },
+                    function(err) {
+                        errorHandling.handleJsonRpcError(err);
+                    }
+                );
+
+            }
+
+            // ---------------------
+            // ACTIONS FOR PACKAGE

             $scope.goEditIcon = function() {
$location.path("/editpkgicon/"+$scope.pkg.name).search({'arch':$routeParams.architectureCode});
             }
+
+            $scope.goEditScreenshots = function() {
+ $location.path("/editpkgscreenshots/"+$scope.pkg.name).search({'arch':$routeParams.architectureCode});
+            }

             $scope.goRemoveIcon = function() {
                 jsonRpc.call(
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/directive/breadcrumbsdirective.js Fri Nov 15 08:51:45 2013 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/breadcrumbsdirective.js Tue Feb 18 00:02:17 2014 UTC
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013, Andrew Lindesay
+ * Copyright 2013-2014, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

@@ -49,7 +49,12 @@
                     });

                     $scope.goItem = function(item) {
-                        $location.path(item.path).search({});
+                        $location.path(item.path);
+
+                        if(item.search) {
+                            $location.search(item.search);
+                        }
+
                         return false;
                     }

=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/directive/validpassworddirective.js Thu Jan 16 08:37:44 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/directive/validpassworddirective.js Tue Feb 18 00:02:17 2014 UTC
@@ -16,7 +16,7 @@

             ctrl.$parsers.unshift(function(value) {

-                var valid = (value.length >= 8)
+                var valid = value && (value.length >= 8)
                     && value.replace(/[^0-9]/g,'').length >= 2
                     && value.replace(/[^A-Z]/g,'').length >= 1;

=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/routes.js Sun Feb 9 10:00:28 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/routes.js Tue Feb 18 00:02:17 2014 UTC
@@ -40,6 +40,7 @@
.when('/viewuser/:nickname',{controller:'ViewUserController', templateUrl:'/js/app/controller/viewuser.html'}) .when('/viewpkg/:name/:version/:architectureCode',{controller:'ViewPkgController', templateUrl:'/js/app/controller/viewpkg.html'}) .when('/editpkgicon/:name',{controller:'EditPkgIconController', templateUrl:'/js/app/controller/editpkgicon.html'}) + .when('/editpkgscreenshots/:name',{controller:'EditPkgScreenshotsController', templateUrl:'/js/app/controller/editpkgscreenshots.html'}) .when('/error',{controller:'ErrorController', templateUrl:'/js/app/controller/error.html'})
                 .when('/',{
                     controller:'HomeController',
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/service/breadcrumbsservice.js Sun Feb 9 10:00:28 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/service/breadcrumbsservice.js Tue Feb 18 00:02:17 2014 UTC
@@ -55,6 +55,27 @@
                   };
                 },

+ createViewPkg : function(pkg,versionType,architectureCode) {
+                    return {
+                        title : pkg.name,
+ path : '/viewpkg/' + pkg.name + '/' + versionType + '/' + architectureCode
+                    };
+                },
+
+                createEditPkgIcon : function(pkg) {
+                    return {
+                        title : 'Edit Icon',
+                        path : '/editpkgicon/' + pkg.name
+                    };
+                },
+
+                createEditPkgScreenshots : function(pkg) {
+                    return {
+                        title : 'Screenshots', // TODO - localize
+                            path : '/editpkgscreenshots/'+pkg.name
+                    };
+                },
+
                 createViewUser : function(user) {
                     return {
                         title : user.nickname,
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/service/pkgiconservice.js Thu Dec 5 09:23:22 2013 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/service/pkgiconservice.js Tue Feb 18 00:02:17 2014 UTC
@@ -1,5 +1,5 @@
 /*
- * Copyright 2013, Andrew Lindesay
+ * Copyright 2013-2014, Andrew Lindesay
  * Distributed under the terms of the MIT License.
  */

@@ -20,7 +20,7 @@
                 errorCodes : {
                     BADFORMATORSIZEERROR : 415,
                     NOTFOUND : 404,
-                    BADREQUEST : 404,
+                    BADREQUEST : 400,
                     UNKNOWN : -1
                 },

=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/service/userstateservice.js Sat Feb 8 09:19:06 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/service/userstateservice.js Tue Feb 18 00:02:17 2014 UTC
@@ -13,8 +13,11 @@

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

             const SIZE_CHECKED_PERMISSION_CACHE = 25;

@@ -208,6 +211,7 @@
// remove the Authorization header for HTTP transport
                             jsonRpc.setHeader('Authorization');
                             pkgIcon.setHeader('Authorization');
+                            pkgScreenshot.setHeader('Authorization');
                         }
                         else {

@@ -219,13 +223,11 @@
throw 'the password clear is required when setting a user';
                             }

-                            jsonRpc.setHeader(
-                                'Authorization',
-                                'Basic 
'+window.btoa(''+value.nickname+':'+value.passwordClear));
+ var basic = 'Basic '+window.btoa(''+value.nickname+':'+value.passwordClear);

-                            pkgIcon.setHeader(
-                                'Authorization',
-                                'Basic 
'+window.btoa(''+value.nickname+':'+value.passwordClear));
+                            jsonRpc.setHeader('Authorization',basic);
+                            pkgIcon.setHeader('Authorization',basic);
+                            pkgScreenshot.setHeader('Authorization',basic);

                             user = value;


==============================================================================
Revision: d1c7e6ee2e39
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Tue Feb 18 00:17:42 2014 UTC
Log:      + modify routes to handle re-navigation to the view pkg page

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

Modified:
/haikudepotserver-webapp/src/main/webapp/js/app/controller/editpkgiconcontroller.js /haikudepotserver-webapp/src/main/webapp/js/app/controller/editpkgscreenshotscontroller.js /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkgcontroller.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/controller/editpkgiconcontroller.js Tue Feb 18 00:02:17 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/editpkgiconcontroller.js Tue Feb 18 00:17:42 2014 UTC
@@ -58,7 +58,10 @@

             function refreshBreadcrumbItems() {
                 $scope.breadcrumbItems = [
- breadcrumbs.createViewPkg($scope.pkg,'latest',$location.search()['arch']),
+                    breadcrumbs.createViewPkg(
+                        $scope.pkg,
+                        $routeParams.version,
+                        $routeParams.architectureCode),
                     breadcrumbs.createEditPkgIcon($scope.pkg)
                 ];
             }
@@ -110,15 +113,7 @@
                             function() {
                                 $scope.amSaving = false;
$log.info('have set the 32px icon for the pkg '+$scope.pkg.name);
-                                var arch = $location.search()['arch'];
-
-                                if(arch) {
- $location.path('/viewpkg/'+$scope.pkg.name+'/latest/'+arch).search({});
-                                }
-                                else {
- $log.info('unable to navigate back to the pkg as no \'arch\' was supplied in the search parameters --> will go home');
-                                    $location.path('/').search({});
-                                }
+ $location.path('/viewpkg/'+$routeParams.name+'/'+$routeParams.version+'/'+$routeParams.architectureCode).search({});
                             },
                             function(e) {
                                 $scope.amSaving = false;
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/editpkgscreenshotscontroller.js Tue Feb 18 00:02:17 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/editpkgscreenshotscontroller.js Tue Feb 18 00:17:42 2014 UTC
@@ -93,7 +93,10 @@

             function refreshBreadcrumbItems() {
                 $scope.breadcrumbItems = [
- breadcrumbs.createViewPkg($scope.pkg,'latest',$location.search()['arch']),
+                    breadcrumbs.createViewPkg(
+                        $scope.pkg,
+                        $routeParams.version,
+                        $routeParams.architectureCode),
                     breadcrumbs.createEditPkgScreenshots($scope.pkg)
                 ];
             }
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkgcontroller.js Tue Feb 18 00:02:17 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/viewpkgcontroller.js Tue Feb 18 00:17:42 2014 UTC
@@ -147,11 +147,11 @@
             // ACTIONS FOR PACKAGE

             $scope.goEditIcon = function() {
- $location.path("/editpkgicon/"+$scope.pkg.name).search({'arch':$routeParams.architectureCode});
+                $location.path($location.path() + '/editicon').search({});
             }

             $scope.goEditScreenshots = function() {
- $location.path("/editpkgscreenshots/"+$scope.pkg.name).search({'arch':$routeParams.architectureCode}); + $location.path($location.path() + '/editscreenshots').search({});
             }

             $scope.goRemoveIcon = function() {
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/routes.js Tue Feb 18 00:02:17 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/routes.js Tue Feb 18 00:17:42 2014 UTC
@@ -39,8 +39,8 @@
.when('/changepassword/:nickname',{controller:'ChangePasswordController', templateUrl:'/js/app/controller/changepassword.html'}) .when('/viewuser/:nickname',{controller:'ViewUserController', templateUrl:'/js/app/controller/viewuser.html'}) .when('/viewpkg/:name/:version/:architectureCode',{controller:'ViewPkgController', templateUrl:'/js/app/controller/viewpkg.html'}) - .when('/editpkgicon/:name',{controller:'EditPkgIconController', templateUrl:'/js/app/controller/editpkgicon.html'}) - .when('/editpkgscreenshots/:name',{controller:'EditPkgScreenshotsController', templateUrl:'/js/app/controller/editpkgscreenshots.html'}) + .when('/viewpkg/:name/:version/:architectureCode/editicon',{controller:'EditPkgIconController', templateUrl:'/js/app/controller/editpkgicon.html'}) + .when('/viewpkg/:name/:version/:architectureCode/editscreenshots',{controller:'EditPkgScreenshotsController', templateUrl:'/js/app/controller/editpkgscreenshots.html'}) .when('/error',{controller:'ErrorController', templateUrl:'/js/app/controller/error.html'})
                 .when('/',{
                     controller:'HomeController',
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/service/breadcrumbsservice.js Tue Feb 18 00:02:17 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/service/breadcrumbsservice.js Tue Feb 18 00:17:42 2014 UTC
@@ -56,6 +56,19 @@
                 },

createViewPkg : function(pkg,versionType,architectureCode) {
+
+                    if(!pkg||!pkg.name||!pkg.name.length) {
+ throw 'the package must be supplied to create a view pkg and must have a name';
+                    }
+
+                    if(!versionType||!versionType.length) {
+ throw 'the versionType must be supplied to create a view pkg';
+                    }
+
+                    if(!architectureCode||!architectureCode.length) {
+ throw 'the architectureCode must be supplied to create a view pkg';
+                    }
+
                     return {
                         title : pkg.name,
path : '/viewpkg/' + pkg.name + '/' + versionType + '/' + architectureCode

==============================================================================
Revision: 2ba6b572106c
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Tue Feb 18 09:54:04 2014 UTC
Log:      + additional data for screenshots
+ avoid the possibility of a rogue client uploading too much data for an icon or screenshot

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

Added:
/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgScreenshotRequest.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgScreenshotResult.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/model/SizeLimitReachedException.java /haikudepotserver-webapp/src/main/resources/db/haikudepot/migration/V1.2__ScreenshotAdditions.sql
 /haikudepotserver-webapp/src/main/webapp/js/app/filter/datalengthfilter.js
Modified:
/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/PkgApi.java /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgScreenshotsResult.java /haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/HpkStringTable.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/PkgApiImpl.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/PkgScreenshot.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/auto/_Pkg.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/auto/_PkgScreenshot.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/PkgService.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/controller/PkgIconController.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/controller/PkgScreenshotController.java
 /haikudepotserver-webapp/src/main/resources/HaikuDepot.map.xml
 /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml
/haikudepotserver-webapp/src/main/webapp/js/app/controller/editpkgiconcontroller.js /haikudepotserver-webapp/src/main/webapp/js/app/controller/editpkgscreenshots.html /haikudepotserver-webapp/src/main/webapp/js/app/controller/editpkgscreenshotscontroller.js

=======================================
--- /dev/null
+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgScreenshotRequest.java Tue Feb 18 09:54:04 2014 UTC
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.pkg;
+
+public class GetPkgScreenshotRequest {
+
+    public String code;
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgScreenshotResult.java Tue Feb 18 09:54:04 2014 UTC
@@ -0,0 +1,15 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.api1.model.pkg;
+
+public class GetPkgScreenshotResult {
+
+    public String code;
+    public Integer length;
+    public Integer height;
+    public Integer width;
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/model/SizeLimitReachedException.java Tue Feb 18 09:54:04 2014 UTC
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+package org.haikuos.haikudepotserver.pkg.model;
+
+import java.io.IOException;
+
+/**
+ * <p>Thrown when more bytes are read from an input than were anticipated.</p>
+ */
+
+public class SizeLimitReachedException extends IOException {
+
+    public SizeLimitReachedException() {
+    }
+
+}
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/resources/db/haikudepot/migration/V1.2__ScreenshotAdditions.sql Tue Feb 18 09:54:04 2014 UTC
@@ -0,0 +1,9 @@
+-- ------------------------------------------------------
+-- ADDITIONAL MATERIAL FOR SCREENSHOTS
+-- ------------------------------------------------------
+
+ALTER TABLE haikudepot.pkg_screenshot ADD COLUMN length INTEGER NOT NULL;
+ALTER TABLE haikudepot.pkg_screenshot ADD COLUMN width INTEGER NOT NULL;
+ALTER TABLE haikudepot.pkg_screenshot ADD COLUMN height INTEGER NOT NULL;
+ALTER TABLE haikudepot.pkg_screenshot ADD COLUMN create_timestamp TIMESTAMP NOT NULL; +ALTER TABLE haikudepot.pkg_screenshot ADD COLUMN modify_timestamp TIMESTAMP NOT NULL;
=======================================
--- /dev/null
+++ /haikudepotserver-webapp/src/main/webapp/js/app/filter/datalengthfilter.js Tue Feb 18 09:54:04 2014 UTC
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2014, Andrew Lindesay
+ * Distributed under the terms of the MIT License.
+ */
+
+/**
+ * <p>This filter will render a human readable string for a data size. It expects a value in bytes and it will
+ * decide if it makes sense to render in MB or GB or...</p>
+ */
+
+angular.module('haikudepotserver').filter('dataLength', function() {
+        return function(input) {
+
+            if(undefined==input||null==input) {
+                return '';
+            }
+
+            var val = !_.isNumber(input) ? input : parseInt(''+input,10);
+
+            if(val < 1024) {
+                return val + ' bytes';
+            }
+
+            if(val < 1024*1024) {
+                return (val/1024).toFixed(1) + ' KB';
+            }
+
+            if(val < 1024*1024*1024) {
+                return (val/(1024*1024)).toFixed(1) + ' MB';
+            }
+
+            return (val/(1024*1024*1024)).toFixed(1) + ' GB';
+        }
+    }
+);
=======================================
--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/PkgApi.java Wed Feb 12 10:07:08 2014 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/PkgApi.java Tue Feb 18 09:54:04 2014 UTC
@@ -36,6 +36,12 @@

RemovePkgIconResult removePkgIcon(RemovePkgIconRequest request) throws ObjectNotFoundException;

+    /**
+     * <p>This method will get the details of a screenshot image.</p>
+     */
+
+ GetPkgScreenshotResult getPkgScreenshot(GetPkgScreenshotRequest request) throws ObjectNotFoundException;
+
     /**
* <p>This method will return an ordered list of the screenshots that are available for this package. It will * throw an {@link org.haikuos.haikudepotserver.api1.support.ObjectNotFoundException} in the case where the
=======================================
--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgScreenshotsResult.java Wed Feb 12 10:07:08 2014 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgScreenshotsResult.java Tue Feb 18 09:54:04 2014 UTC
@@ -13,6 +13,9 @@

     public static class PkgScreenshot {
         public String code;
+        public Integer length;
+        public Integer height;
+        public Integer width;
     }

 }
=======================================
--- /haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/HpkStringTable.java Tue Feb 18 00:02:17 2014 UTC +++ /haikudepotserver-packagefile/src/main/java/org/haikuos/pkg/HpkStringTable.java Tue Feb 18 09:54:04 2014 UTC
@@ -58,9 +58,6 @@
                 new HeapCoordinates(
                         heapOffset,
                         heapLength));
-
-//        try { Files.write(stringsDataBuffer, new File("/tmp/dat")); }
-//        catch(IOException ioe) {}

         // now work through the data and load them into the strings.

=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/PkgApiImpl.java Tue Feb 18 00:02:17 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/api1/PkgApiImpl.java Tue Feb 18 09:54:04 2014 UTC
@@ -280,6 +280,26 @@

         return new RemovePkgIconResult();
     }
+
+    @Override
+ public GetPkgScreenshotResult getPkgScreenshot(GetPkgScreenshotRequest request) throws ObjectNotFoundException {
+        Preconditions.checkNotNull(request);
+        Preconditions.checkNotNull(request.code);
+
+        final ObjectContext context = serverRuntime.getContext();
+ Optional<PkgScreenshot> pkgScreenshotOptional = PkgScreenshot.getByCode(context, request.code);
+
+        if(!pkgScreenshotOptional.isPresent()) {
+ throw new ObjectNotFoundException(PkgScreenshot.class.getSimpleName(), request.code);
+        }
+
+        GetPkgScreenshotResult result = new GetPkgScreenshotResult();
+        result.code = pkgScreenshotOptional.get().getCode();
+        result.height = pkgScreenshotOptional.get().getHeight();
+        result.width = pkgScreenshotOptional.get().getWidth();
+        result.length = pkgScreenshotOptional.get().getLength();
+        return result;
+    }

     @Override
public GetPkgScreenshotsResult getPkgScreenshots(GetPkgScreenshotsRequest getPkgScreenshotsRequest) throws ObjectNotFoundException {
@@ -301,6 +321,9 @@
public GetPkgScreenshotsResult.PkgScreenshot apply(PkgScreenshot pkgScreenshot) { GetPkgScreenshotsResult.PkgScreenshot rs = new GetPkgScreenshotsResult.PkgScreenshot();
                         rs.code = pkgScreenshot.getCode();
+                        rs.height = pkgScreenshot.getHeight();
+                        rs.width = pkgScreenshot.getWidth();
+                        rs.length = pkgScreenshot.getLength();
                         return rs;
                     }
                 }
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/PkgScreenshot.java Wed Feb 12 10:07:08 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/PkgScreenshot.java Tue Feb 18 09:54:04 2014 UTC
@@ -12,11 +12,14 @@
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.exp.ExpressionFactory;
 import org.apache.cayenne.query.SelectQuery;
+import org.apache.cayenne.validation.BeanValidationFailure;
+import org.apache.cayenne.validation.ValidationResult;
 import org.haikuos.haikudepotserver.dataobjects.auto._PkgScreenshot;
+import org.haikuos.haikudepotserver.dataobjects.support.CreateAndModifyTimestamped;

 import java.util.List;

-public class PkgScreenshot extends _PkgScreenshot implements Comparable<PkgScreenshot> { +public class PkgScreenshot extends _PkgScreenshot implements Comparable<PkgScreenshot>, CreateAndModifyTimestamped {

public static Optional<PkgScreenshot> getByCode(ObjectContext context, String code) {
         Preconditions.checkNotNull(context);
@@ -48,4 +51,22 @@
     public int compareTo(PkgScreenshot o) {
         return getOrdering().compareTo(o.getOrdering());
     }
+
+    @Override
+    protected void validateForSave(ValidationResult validationResult) {
+        super.validateForSave(validationResult);
+
+        if(getHeight() <= 0) {
+ validationResult.addFailure(new BeanValidationFailure(this,HEIGHT_PROPERTY,"range"));
+        }
+
+        if(getWidth() <= 0) {
+ validationResult.addFailure(new BeanValidationFailure(this,WIDTH_PROPERTY,"range"));
+        }
+
+        if(getLength() <= 0) {
+ validationResult.addFailure(new BeanValidationFailure(this,LENGTH_PROPERTY,"range"));
+        }
+    }
+
 }
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/auto/_Pkg.java Wed Dec 11 08:25:33 2013 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/auto/_Pkg.java Tue Feb 18 09:54:04 2014 UTC
@@ -60,7 +60,6 @@
     public void removeFromPkgIcons(PkgIcon obj) {
         removeToManyTarget(PKG_ICONS_PROPERTY, obj, true);
     }
-
     @SuppressWarnings("unchecked")
     public List<PkgIcon> getPkgIcons() {
         return (List<PkgIcon>)readProperty(PKG_ICONS_PROPERTY);
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/auto/_PkgScreenshot.java Wed Dec 11 08:25:33 2013 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/dataobjects/auto/_PkgScreenshot.java Tue Feb 18 09:54:04 2014 UTC
@@ -1,5 +1,6 @@
 package org.haikuos.haikudepotserver.dataobjects.auto;

+import java.util.Date;
 import java.util.List;

 import org.haikuos.haikudepotserver.dataobjects.Pkg;
@@ -15,7 +16,12 @@
 public abstract class _PkgScreenshot extends AbstractDataObject {

     public static final String CODE_PROPERTY = "code";
+ public static final String CREATE_TIMESTAMP_PROPERTY = "createTimestamp";
+    public static final String HEIGHT_PROPERTY = "height";
+    public static final String LENGTH_PROPERTY = "length";
+ public static final String MODIFY_TIMESTAMP_PROPERTY = "modifyTimestamp";
     public static final String ORDERING_PROPERTY = "ordering";
+    public static final String WIDTH_PROPERTY = "width";
     public static final String PKG_PROPERTY = "pkg";
public static final String PKG_SCREENSHOT_IMAGES_PROPERTY = "pkgScreenshotImages";

@@ -27,6 +33,34 @@
     public String getCode() {
         return (String)readProperty(CODE_PROPERTY);
     }
+
+    public void setCreateTimestamp(Date createTimestamp) {
+        writeProperty(CREATE_TIMESTAMP_PROPERTY, createTimestamp);
+    }
+    public Date getCreateTimestamp() {
+        return (Date)readProperty(CREATE_TIMESTAMP_PROPERTY);
+    }
+
+    public void setHeight(Integer height) {
+        writeProperty(HEIGHT_PROPERTY, height);
+    }
+    public Integer getHeight() {
+        return (Integer)readProperty(HEIGHT_PROPERTY);
+    }
+
+    public void setLength(Integer length) {
+        writeProperty(LENGTH_PROPERTY, length);
+    }
+    public Integer getLength() {
+        return (Integer)readProperty(LENGTH_PROPERTY);
+    }
+
+    public void setModifyTimestamp(Date modifyTimestamp) {
+        writeProperty(MODIFY_TIMESTAMP_PROPERTY, modifyTimestamp);
+    }
+    public Date getModifyTimestamp() {
+        return (Date)readProperty(MODIFY_TIMESTAMP_PROPERTY);
+    }

     public void setOrdering(Integer ordering) {
         writeProperty(ORDERING_PROPERTY, ordering);
@@ -34,6 +68,13 @@
     public Integer getOrdering() {
         return (Integer)readProperty(ORDERING_PROPERTY);
     }
+
+    public void setWidth(Integer width) {
+        writeProperty(WIDTH_PROPERTY, width);
+    }
+    public Integer getWidth() {
+        return (Integer)readProperty(WIDTH_PROPERTY);
+    }

     public void setPkg(Pkg pkg) {
         setToOneTarget(PKG_PROPERTY, pkg, true);
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/PkgService.java Tue Feb 18 00:02:17 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/PkgService.java Tue Feb 18 09:54:04 2014 UTC
@@ -23,6 +23,7 @@
 import org.haikuos.haikudepotserver.pkg.model.BadPkgIconException;
 import org.haikuos.haikudepotserver.pkg.model.BadPkgScreenshotException;
 import org.haikuos.haikudepotserver.pkg.model.PkgSearchSpecification;
+import org.haikuos.haikudepotserver.pkg.model.SizeLimitReachedException;
 import org.haikuos.haikudepotserver.support.Closeables;
 import org.haikuos.haikudepotserver.support.ImageHelper;
 import org.haikuos.haikudepotserver.support.cayenne.LikeHelper;
@@ -50,7 +51,41 @@

     protected static int SCREENSHOT_SIDE_LIMIT = 1500;

+ // these seem like reasonable limits for the size of image data to have to
+    // handle in-memory.
+
+    protected static int SCREENSHOT_SIZE_LIMIT = 2 * 1024 * 1024; // 2MB
+    protected static int ICON_SIZE_LIMIT = 100 * 1024; // 100k
+
     private ImageHelper imageHelper = new ImageHelper();
+
+    // ------------------------------
+    // HELP
+
+    /**
+ * <p>This method will read in the quantity of bytes from the input stream upto the limit. If the limit is + * reached, the method will throw {@link org.haikuos.haikudepotserver.pkg.model.SizeLimitReachedException}.</p>
+     */
+
+ public static byte[] toByteArray(InputStream inputStream, int sizeLimit) throws IOException {
+        Preconditions.checkNotNull(inputStream);
+        Preconditions.checkState(sizeLimit > 0);
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        byte[] buffer = new byte[8*1024];
+        int read;
+
+        while(-1 != (read = inputStream.read(buffer,0,buffer.length))) {
+
+            if(read + baos.size() > sizeLimit) {
+                throw new SizeLimitReachedException();
+            }
+
+            baos.write(buffer,0,read);
+        }
+
+        return baos.toByteArray();
+    }

     // ------------------------------
     // SEARCH
@@ -245,7 +280,7 @@
         Preconditions.checkNotNull(pkg);
         Preconditions.checkState(16==expectedSize||32==expectedSize);

-        byte[] pngData = ByteStreams.toByteArray(input);
+        byte[] pngData = toByteArray(input, ICON_SIZE_LIMIT);
         ImageHelper.Size size =  imageHelper.derivePngSize(pngData);

         if(null==size) {
@@ -349,7 +384,7 @@
         Preconditions.checkNotNull(context);
         Preconditions.checkNotNull(pkg);

-        byte[] pngData = ByteStreams.toByteArray(input);
+        byte[] pngData = toByteArray(input, SCREENSHOT_SIZE_LIMIT);
         ImageHelper.Size size =  imageHelper.derivePngSize(pngData);

         if(null==size) {
@@ -380,6 +415,9 @@
         PkgScreenshot screenshot = context.newObject(PkgScreenshot.class);
         screenshot.setCode(UUID.randomUUID().toString());
         screenshot.setOrdering(new Integer(ordering));
+        screenshot.setHeight(size.height);
+        screenshot.setWidth(size.width);
+        screenshot.setLength(pngData.length);
pkg.addToManyTarget(Pkg.PKG_SCREENSHOTS_PROPERTY, screenshot, true);

PkgScreenshotImage screenshotImage = context.newObject(PkgScreenshotImage.class);
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/controller/PkgIconController.java Wed Feb 12 10:07:08 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/controller/PkgIconController.java Tue Feb 18 09:54:04 2014 UTC
@@ -11,6 +11,7 @@
 import com.google.common.net.MediaType;
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.configuration.server.ServerRuntime;
+import org.haikuos.haikudepotserver.pkg.model.SizeLimitReachedException;
 import org.haikuos.haikudepotserver.security.model.Permission;
 import org.haikuos.haikudepotserver.security.AuthorizationService;
 import org.haikuos.haikudepotserver.support.ByteCounterOutputStream;
@@ -192,6 +193,10 @@
                     context,
                     pkg.get());
         }
+        catch(SizeLimitReachedException sizeLimit) {
+ logger.warn("attempt to load in an icon file that is larger than that allowed");
+            throw new MissingOrBadFormat();
+        }
         catch(BadPkgIconException badIcon) {
             throw new MissingOrBadFormat();
         }
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/controller/PkgScreenshotController.java Tue Feb 18 00:02:17 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/controller/PkgScreenshotController.java Tue Feb 18 09:54:04 2014 UTC
@@ -16,6 +16,7 @@
 import org.haikuos.haikudepotserver.dataobjects.User;
 import org.haikuos.haikudepotserver.pkg.PkgService;
 import org.haikuos.haikudepotserver.pkg.model.BadPkgScreenshotException;
+import org.haikuos.haikudepotserver.pkg.model.SizeLimitReachedException;
 import org.haikuos.haikudepotserver.security.AuthorizationService;
 import org.haikuos.haikudepotserver.security.model.Permission;
 import org.haikuos.haikudepotserver.support.ByteCounterOutputStream;
@@ -62,8 +63,8 @@
     public void fetchHead(
             HttpServletRequest request,
             HttpServletResponse response,
- @RequestParam(value = KEY_TARGETWIDTH, required=true) Integer targetWidth, - @RequestParam(value = KEY_TARGETHEIGHT, required=true) Integer targetHeight,
+            @RequestParam(value = KEY_TARGETWIDTH) Integer targetWidth,
+            @RequestParam(value = KEY_TARGETHEIGHT) Integer targetHeight,
             @PathVariable(value = KEY_FORMAT) String format,
@PathVariable(value = KEY_SCREENSHOTCODE) String screenshotCode)
             throws IOException {
@@ -255,6 +256,10 @@
                     context,
                     pkg.get()).getCode();
         }
+        catch(SizeLimitReachedException sizeLimit) {
+ logger.warn("attempt to load in a screenshot larger than the size limit");
+            throw new MissingOrBadFormat();
+        }
         catch(BadPkgScreenshotException badIcon) {
             throw new MissingOrBadFormat();
         }
=======================================
--- /haikudepotserver-webapp/src/main/resources/HaikuDepot.map.xml Wed Feb 12 10:07:08 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/HaikuDepot.map.xml Tue Feb 18 09:54:04 2014 UTC
@@ -61,9 +61,14 @@
        </db-entity>
        <db-entity name="pkg_screenshot" schema="haikudepot">
                <db-attribute name="code" type="CHAR" isMandatory="true" 
length="36"/>
+ <db-attribute name="create_timestamp" type="TIMESTAMP" isMandatory="true"/>
+               <db-attribute name="height" type="INTEGER" isMandatory="true"/>
<db-attribute name="id" type="BIGINT" isPrimaryKey="true" isMandatory="true"/>
+               <db-attribute name="length" type="INTEGER" isMandatory="true"/>
+ <db-attribute name="modify_timestamp" type="TIMESTAMP" isMandatory="true"/>
                <db-attribute name="ordering" type="INTEGER" 
isMandatory="true"/>
                <db-attribute name="pkg_id" type="BIGINT" isMandatory="true"/>
+               <db-attribute name="width" type="INTEGER" isMandatory="true"/>
                <db-key-generator>
                        <db-generator-type>ORACLE</db-generator-type>
                        
<db-generator-name>haikudepot.pkg_screenshot_seq</db-generator-name>
@@ -218,7 +223,16 @@
        </obj-entity>
<obj-entity name="PkgScreenshot" className="org.haikuos.haikudepotserver.dataobjects.PkgScreenshot" lock-type="optimistic" dbEntityName="pkg_screenshot" superClassName="org.haikuos.haikudepotserver.dataobjects.support.AbstractDataObject"> <obj-attribute name="code" type="java.lang.String" db-attribute-path="code"/> + <obj-attribute name="createTimestamp" type="java.util.Date" db-attribute-path="create_timestamp"/> + <obj-attribute name="height" type="java.lang.Integer" db-attribute-path="height"/> + <obj-attribute name="length" type="java.lang.Integer" db-attribute-path="length"/> + <obj-attribute name="modifyTimestamp" type="java.util.Date" db-attribute-path="modify_timestamp"/> <obj-attribute name="ordering" type="java.lang.Integer" db-attribute-path="ordering"/> + <obj-attribute name="width" type="java.lang.Integer" db-attribute-path="width"/> + <entity-listener class="org.haikuos.haikudepotserver.support.cayenne.PostAddCreateAndModifyTimestampListener">
+                       <post-add method-name="onPostAdd"/>
+                       <pre-update method-name="onPreUpdate"/>
+               </entity-listener>
        </obj-entity>
<obj-entity name="PkgScreenshotImage" className="org.haikuos.haikudepotserver.dataobjects.PkgScreenshotImage" dbEntityName="pkg_screenshot_image" superClassName="org.haikuos.haikudepotserver.dataobjects.support.AbstractDataObject">
                <obj-attribute name="data" type="byte[]" 
db-attribute-path="data"/>
=======================================
--- /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml Tue Feb 18 00:02:17 2014 UTC +++ /haikudepotserver-webapp/src/main/resources/spring/webresourcegroup.xml Tue Feb 18 09:54:04 2014 UTC
@@ -89,6 +89,7 @@
<value>/js/app/service/errorhandlingservice.js</value>

<value>/js/app/filter/timestampfilter.js</value> + <value>/js/app/filter/datalengthfilter.js</value>

                         </list>
                     </property>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/editpkgiconcontroller.js Tue Feb 18 00:17:42 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/editpkgiconcontroller.js Tue Feb 18 09:54:04 2014 UTC
@@ -14,6 +14,8 @@
             jsonRpc,constants,pkgIcon,errorHandling,
             breadcrumbs) {

+            var ICON_SIZE_LIMIT = 100 * 1024; // 100k
+
             $scope.breadcrumbItems = undefined;
             $scope.pkg = undefined;
             $scope.amSaving = false;
@@ -74,7 +76,7 @@

             function validateIconFile(file, model) {
                 model.$setValidity('badformatorsize',true);
- model.$setValidity('badsize',undefined==file || (file.size
24 && file.size < 32*1024));
+ model.$setValidity('badsize',undefined==file || (file.size
24 && file.size < ICON_SIZE_LIMIT));
             }

             function icon32FileDidChange() {
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/editpkgscreenshots.html Tue Feb 18 00:02:17 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/editpkgscreenshots.html Tue Feb 18 09:54:04 2014 UTC
@@ -21,7 +21,7 @@
         <div ng-repeat="pkgScreenshot in pkgScreenshots">
             <img ng-src="{{pkgScreenshot.imageThumbnailUrl}}"></img>
             <div class="pkg-screenshot-controls">
-                <div><strong>#{{$index+1}}</strong></div>
+ <div><strong>#{{$index+1}}</strong> - ({{pkgScreenshot.width}} x {{pkgScreenshot.height}}), {{pkgScreenshot.length|dataLength}}</div>

                 <ul>
<li><a href="" ng-click="goRemovePkgScreenshot(pkgScreenshot)">Remove</a></li>
=======================================
--- /haikudepotserver-webapp/src/main/webapp/js/app/controller/editpkgscreenshotscontroller.js Tue Feb 18 00:17:42 2014 UTC +++ /haikudepotserver-webapp/src/main/webapp/js/app/controller/editpkgscreenshotscontroller.js Tue Feb 18 09:54:04 2014 UTC
@@ -17,7 +17,7 @@
// the upload size must be less than this or it is too big for the
             // far end to process.

-            var FILESIZEMAX = 2 * 1024 * 1024;
+            var SCREENSHOT_SIZE_LIMIT = 2 * 1024 * 1024; // 2MB

             var THUMBNAIL_TARGETWIDTH = 180;
             var THUMBNAIL_TARGETHEIGHT = 90;
@@ -51,6 +51,9 @@
$scope.pkgScreenshots = _.map(result.items, function(item) {
                             return {
                                 code : item.code,
+                                width : item.width,
+                                height : item.height,
+                                length : item.length,
imageThumbnailUrl : pkgScreenshot.url($scope.pkg,item.code,THUMBNAIL_TARGETWIDTH,THUMBNAIL_TARGETHEIGHT), imageDownloadUrl : pkgScreenshot.rawUrl($scope.pkg,item.code)
                             };
@@ -114,7 +117,7 @@
                 var file = $scope.addPkgScreenshot.file;
                 var model = $scope.addPkgScreenshotForm['file']
                 model.$setValidity('badformatorsize',true);
- model.$setValidity('badsize',undefined==file || (file.size
24 && file.size < FILESIZEMAX));
+ model.$setValidity('badsize',undefined==file || (file.size
24 && file.size < SCREENSHOT_SIZE_LIMIT));
             });

// This function will take the data from the form and will create the user from this data.
@@ -134,11 +137,28 @@
$log.info('have added a screenshot for the pkg '+$scope.pkg.name);
                         $scope.addPkgScreenshot.file = undefined;
                         $scope.addPkgScreenshotForm.$setPristine();
-                        $scope.pkgScreenshots.push({
-                            code : code,
- imageThumbnailUrl : pkgScreenshot.url($scope.pkg,code,THUMBNAIL_TARGETWIDTH,THUMBNAIL_TARGETHEIGHT), - imageDownloadUrl : pkgScreenshot.rawUrl($scope.pkg,code)
-                        })
+
+                        jsonRpc.call(
+                                constants.ENDPOINT_API_V1_PKG,
+                                "getPkgScreenshot",
+                                [{ code : code }]
+                            ).then(
+                            function(result) {
+                                $scope.pkgScreenshots.push({
+                                    code : code,
+                                    height : result.height,
+                                    width : result.width,
+                                    length : result.length,
+ imageThumbnailUrl : pkgScreenshot.url($scope.pkg,code,THUMBNAIL_TARGETWIDTH,THUMBNAIL_TARGETHEIGHT), + imageDownloadUrl : pkgScreenshot.rawUrl($scope.pkg,code)
+                                });
+
+                                $scope.amCommunicating = false;
+                            },
+                            function(err) {
+                                errorHandling.handleJsonRpcError(err);
+                            }
+                        );
                     },
                     function(e) {
if(e==pkgScreenshot.errorCodes.BADFORMATORSIZEERROR) {
@@ -148,13 +168,11 @@
$log.error('unable to add the screenshot for; '+$scope.pkg.name);
                             $location.path('/error').search({});
                         }
-                    }
-                )['finally'](
-                    function() {
+
                         $scope.amCommunicating = false;

                     }
-                )
+                );
             }

             // -------------------------

==============================================================================
Revision: 99da10a910a9
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Tue Feb 18 10:09:54 2014 UTC
Log:      + documentation update
+ additional integration test coverage

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

Modified:
/haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgScreenshotRequest.java
 /haikudepotserver-docs/src/main/latex/docs/part-api.tex
/haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotsever/api1/PkgApiIT.java

=======================================
--- /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgScreenshotRequest.java Tue Feb 18 09:54:04 2014 UTC +++ /haikudepotserver-api1/src/main/java/org/haikuos/haikudepotserver/api1/model/pkg/GetPkgScreenshotRequest.java Tue Feb 18 10:09:54 2014 UTC
@@ -9,4 +9,11 @@

     public String code;

+    public GetPkgScreenshotRequest() {
+    }
+
+    public GetPkgScreenshotRequest(String code) {
+        this.code = code;
+    }
+
 }
=======================================
--- /haikudepotserver-docs/src/main/latex/docs/part-api.tex Wed Feb 12 10:07:08 2014 UTC +++ /haikudepotserver-docs/src/main/latex/docs/part-api.tex Tue Feb 18 10:09:54 2014 UTC
@@ -216,8 +216,8 @@
   \end{itemize}
 \item Expected HTTP Status Codes
   \begin{itemize}
-  \item {\bf 200} : The icon is provided in the response (for GET)
- \item {\bf 415} : The path did not include ".png" or the target width or height is invalid
+  \item {\bf 200} : The image data is provided in the response (for GET)
+ \item {\bf 415} : The path did not include ".png" or the target width or height is invalid or the length of the data is too large
   \item {\bf 400} : The screenshot code was not supplied
   \item {\bf 404} : The screenshot was not found
   \end{itemize}
@@ -226,6 +226,25 @@
 An example URL is;

\framebox{\tt http://localhost:8080/pkgscreenshot/a78hw20fh2p20fh122jd92.png?tw=640\&th=480}
+
+\subsubsection{Get Raw Screenshot Image}
+
+This API is able to provide the {\it raw} screenshot data.
+
+\begin{itemize}
+\item HTTP Method : GET
+\item Path : /pkgscreenshot/raw/$<$screenshotcode$>$
+\item Response Content-Type : {\it As per the stored data}
+\item Expected HTTP Status Codes
+  \begin{itemize}
+  \item {\bf 200} : The image data is provided in the response (for GET)
+  \item {\bf 404} : The screenshot was not found
+  \end{itemize}
+\end{itemize}
+
+An example URL is;
+
+\framebox{\tt http://localhost:8080/pkgscreenshot/raw/a78hw20fh2p20fh122jd92}

 \subsubsection{Put Screenshot Image}

@@ -237,7 +256,7 @@
 \item Expected HTTP Status Codes
   \begin{itemize}
   \item {\bf 200} : The screenshot image was stored
- \item {\bf 415} : The path did not include ".png" or the size of the image is invalid or the payload is not PNG image data. + \item {\bf 415} : The path did not include ".png" or the size (pixels or data) of the image is invalid or the payload is not PNG image data. \item {\bf 404} : The package identified in the path was not able to be found
   \item {\bf 400} : The package name was not supplied
   \end{itemize}
=======================================
--- /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotsever/api1/PkgApiIT.java Fri Feb 14 08:42:33 2014 UTC +++ /haikudepotserver-webapp/src/test/java/org/haikuos/haikudepotsever/api1/PkgApiIT.java Tue Feb 18 10:09:54 2014 UTC
@@ -154,8 +154,30 @@
             PkgScreenshot pkgScreenshot = sortedScreenshots.get(i);
GetPkgScreenshotsResult.PkgScreenshot apiPkgScreenshot = result.items.get(i); Assertions.assertThat(pkgScreenshot.getCode()).isEqualTo(apiPkgScreenshot.code);
+            Assertions.assertThat(pkgScreenshot.getWidth()).isEqualTo(320);
+ Assertions.assertThat(pkgScreenshot.getHeight()).isEqualTo(240); + Assertions.assertThat(pkgScreenshot.getLength()).isEqualTo(41296);
         }
     }
+
+    /**
+ * <p>This test depends on the sample package pkg1 having some screenshots associated with it.</p>
+     */
+
+    @Test
+    public void testGetPkgScreenshot() throws ObjectNotFoundException {
+ IntegrationTestSupportService.StandardTestData data = integrationTestSupportService.createStandardTestData();
+        String code = data.pkg1.getSortedPkgScreenshots().get(0).getCode();
+
+        // ------------------------------------
+ GetPkgScreenshotResult result = pkgApi.getPkgScreenshot(new GetPkgScreenshotRequest(code));
+        // ------------------------------------
+
+        Assertions.assertThat(result.code).isEqualTo(code);
+        Assertions.assertThat(result.width).isEqualTo(320);
+        Assertions.assertThat(result.height).isEqualTo(240);
+        Assertions.assertThat(result.length).isEqualTo(41296);
+    }

     /**
* <p>This test depends on the sample package pkg1 having some screenshots associated with it.</p>

==============================================================================
Revision: 4ee4560ddc66
Author:   Andrew Lindesay <apl@xxxxxxxxxxxxxx>
Date:     Tue Feb 18 10:16:48 2014 UTC
Log:      + cache control on icons and screenshots

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

Modified:
/haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/controller/PkgIconController.java /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/controller/PkgScreenshotController.java

=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/controller/PkgIconController.java Tue Feb 18 09:54:04 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/controller/PkgIconController.java Tue Feb 18 10:16:48 2014 UTC
@@ -140,6 +140,7 @@
         }

         response.setContentType(MediaType.PNG.toString());
+        response.setHeader(HttpHeaders.CACHE_CONTROL, "max-age=3600");
response.setDateHeader(HttpHeaders.LAST_MODIFIED, pkg.get().getModifyTimestampSecondAccuracy().getTime());

         pkgService.writePkgIconImage(
=======================================
--- /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/controller/PkgScreenshotController.java Tue Feb 18 09:54:04 2014 UTC +++ /haikudepotserver-webapp/src/main/java/org/haikuos/haikudepotserver/pkg/controller/PkgScreenshotController.java Tue Feb 18 10:16:48 2014 UTC
@@ -151,6 +151,7 @@
         }

         response.setContentType(MediaType.PNG.toString());
+        response.setHeader(HttpHeaders.CACHE_CONTROL, "max-age=3600");
         response.setDateHeader(
                 HttpHeaders.LAST_MODIFIED,
screenshotOptional.get().getPkg().getModifyTimestampSecondAccuracy().getTime());

Other related posts:

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