browser staff : make accessing remote vs local actions possible
authorBill Erickson <berick@esilibrary.com>
Fri, 25 Apr 2014 14:52:19 +0000 (10:52 -0400)
committerBill Erickson <berick@esilibrary.com>
Fri, 25 Apr 2014 14:52:19 +0000 (10:52 -0400)
Signed-off-by: Bill Erickson <berick@esilibrary.com>
Open-ILS/web/js/ui/default/staff/services/printstore.js

index c53a2f0..495a610 100644 (file)
@@ -29,42 +29,41 @@ angular.module('egCoreMod')
     service.pending = [];
     service.socket = null;
     service.hatchAvailable = null;
-    service.hatchURL = 'wss://localhost:8443/hatch'; 
+    service.defaultHatchURL = 'wss://localhost:8443/hatch'; 
     service.hatchRequired = false;
-    // TODO: would be nice to support local fall-through for specific actions
 
     // write a message to the Hatch websocket
     service.sendToHatch = function(msg) {
         var msg2 = {};
+
         // shallow copy and scrub msg before sending
         angular.forEach(msg, function(val, key) {
             if (key.match(/deferred/)) return;
             msg2[key] = val;
         });
-        console.debug("sending '" + msg.action + "' command to Hatch");
+
         console.debug("sending to Hatch: " + JSON.stringify(msg2,null,2));
         service.socket.send(JSON.stringify(msg2));
     }
 
     // Send the request to Hatch if it's available.  
     // Otherwise handle the request locally.
-    service.dispatchRequest = function(msg) {
+    service.attemptHatchDelivery = function(msg) {
 
         msg.msgid = service.msgId++;
         msg.deferred = $q.defer();
         service.messages[msg.msgid] = msg;
 
-        if (service.hatchAvailable === false) {
-            // Hatch is known to be closed
-            service.pending.push(msg);
-            service.handleRequestsLocally();
+        if (service.hatchAvailable === false) { // Hatch is closed
+            msg.deferred.reject(msg);
+            //service.pending.push(msg);
+            //service.handleRequestsLocally();
 
-        } else if (service.hatchAvailable === true) {
+        } else if (service.hatchAvailable === true) { // Hatch is open
             // Hatch is known to be open
             service.sendToHatch(msg);
 
-        } else {
-            // Hatch status unknown; attempt to connect
+        } else {  // Hatch status unknown; attempt to connect
             service.pending.push(msg);
             service.hatchConnect();
         }
@@ -72,6 +71,7 @@ angular.module('egCoreMod')
         return msg.deferred.promise;
     }
 
+
     // resolve the promise on the given request and remove
     // it from our tracked requests.
     service.resolveRequest = function(msg) {
@@ -79,6 +79,7 @@ angular.module('egCoreMod')
         if (!service.messages[msg.msgid]) {
             console.warn('no cached message for ' 
                 + msg.msgid + ' : ' + JSON.stringify(msg, null, 2));
+            return;
         }
 
         // for requests sent through Hatch, only the cached 
@@ -88,9 +89,9 @@ angular.module('egCoreMod')
 
         // resolve / reject
         if (msg.error) {
-            console.error("egPrintStore command failed : " 
+            throw new Error(
+            "egPrintStore command failed : " 
                 + JSON.stringify(msg.error, null, 2));
-            msg.deferred.reject(msg.error);
         } else {
             msg.deferred.resolve(msg.content);
         } 
@@ -111,29 +112,29 @@ angular.module('egCoreMod')
                 );
             }
 
-            switch(msg.action) {
-                case 'print':
-                    service.browserPrint(msg);
-                    break;
-                case 'keys':
-                case 'get':
-                case 'set':
-                case 'append':
-                case 'remove':
-                    service.handleLocalStorageRequest(msg);
-                    break;
-                case 'printers' : 
-                    // there is fall-through handler for printers
-                    msg.content = [];
-                    break;
-                default:
-                    console.debug(
-                        'no fall-thru handler for requested action: ' + msg.action);
+            if (msg.localHandler) {
+                msg.content = msg.localHandler(msg);
+            } else {
+                msg.error = 
+                    'no fall-thru handler for requested action: ' 
+                    + msg.action;
             }
+
             service.resolveRequest(msg); 
         }
     }
 
+    service.hatchClosed = function() {
+        console.debug("Hatch closing...");
+        service.socket = null;
+        service.printers = [];
+        service.printConfig = {};
+        while ( (msg = service.pending.shift()) ) 
+            msg.deferred.reject(msg);
+        if (service.onHatchClose)
+            service.onHatchClose();
+    }
+
     service.hatchConnect = function() {
 
         if (service.socket && 
@@ -146,9 +147,12 @@ angular.module('egCoreMod')
         console.debug("connecting to Hatch...");
 
         try {
-            service.socket = new WebSocket(service.hatchURL);
+            var url = service.getLocalItem('eg.hatch.url') 
+                || service.defaultHatchURL;
+            service.socket = new WebSocket(url);
         } catch(e) {
-            service.handleRequestsLocally();
+            service.hatchAvailable = false;
+            service.hatchClosed();
             return;
         }
 
@@ -164,29 +168,24 @@ angular.module('egCoreMod')
 
         service.socket.onclose = function() {
             if (service.hatchAvailable === false) return; // already registered
-            console.debug("disconnected from Hatch");
-            service.socket = null;
-            service.hatchAvailable = null; // reset
-            if (service.onHatchClose)
-                service.onHatchClose();
+
+            // onclose() will be called regularly as we disconnect from
+            // Hatch via timeouts.  Return hatchAvailable to its unknow state
+            service.hatchAvailable = null;
+            service.hatchClosed();
         }
 
         service.socket.onerror = function() {
             if (service.hatchAvailable === false) return; // already registered
+            service.hatchAvailable = false;
             console.debug(
                 "unable to connect to Hatch server at " + service.hatchURL);
-            service.handleRequestsLocally();
-            if (service.onHatchClose)
-                service.onHatchClose();
+            service.hatchClosed();
         }
 
         service.socket.onmessage = function(evt) {
             var msgStr = evt.data;
-
-            if (!msgStr) {
-                console.error("Hatch returned empty message");
-                return;
-            }
+            if (!msgStr) throw new Error("Hatch returned empty message");
 
             var msgObj = JSON.parse(msgStr);
             console.debug('Hatch says ' + JSON.stringify(msgObj, null, 2));
@@ -194,15 +193,6 @@ angular.module('egCoreMod')
         }
     }
 
-    // print locally via the browser
-    service.browserPrint = function(msg) {
-        service.onBrowserPrint(msg.contentType, msg.content);
-    }
-
-    /**
-     * TODO: local and hatch templates need to go through generation..
-     * */
-
     service.interpolateHtmlTemplate = function(template, printScope) {
         // TODO: for print template security, we must scrub
         // the scope object and remove any references to 
@@ -216,43 +206,64 @@ angular.module('egCoreMod')
     }
 
 
-    // print the provided content
     // supported values for contentType are 'text/html' and 'text/plain'
     service.print = function(
         context, contentType, content, printScope, withDialog) {
 
+        // generate our HTML content if necessary
         if (contentType == 'text/html') {
             content = service.interpolateHtmlTemplate(content, printScope);
             console.debug('generated HTML ' + content);
         }
 
-        return service.getPrintConfig()
-        .then(function(conf) {
-            return service.dispatchRequest({
-                key : 'no-op', 
-                action : 'print',
-                config : conf[context],
-                content : content, 
-                contentType : contentType,
-                showDialog : withDialog
-            });
-        });
+        return service.remotePrint(
+            context, contentType, content, printScope, withDialog)['catch'](
+            function(msg) {
+                // remote print not available; print locally
+                return service.browserPrint(msg);
+            }
+        );
+    }
+
+    service.remotePrint = function(
+        context, contentType, content, printScope, withDialog) {
+
+        return service.getPrintConfig().then(
+            function(conf) {
+                // print configuration retrieved; print
+                return service.attemptHatchDelivery({
+                    action : 'print',
+                    config : conf[context],
+                    content : content, 
+                    contentType : contentType,
+                    showDialog : withDialog,
+                });
+            }
+        );
     }
 
+    // print locally via the browser
+    service.browserPrint = function(msg) {
+        service.onBrowserPrint(msg.contentType, msg.content);
+    }
+
+    // -------------
+    // print configuration is always stored as remote items,
+    // since there is no concept of a local printer
     service.getPrintConfig = function() {
         if (service.printConfig) 
             return $q.when(service.printConfig);
 
-        return service.getItem('eg.printing.config')
+        return service.getRemoteItem('eg.printing.config')
         .then(function(conf) { 
             return (service.printConfig = conf || {}) 
         });
     }
-
     service.setPrintConfig = function(conf) {
         service.printConfig = conf;
-        return service.setItem('eg.printing.config', conf);
+        return service.setRemoteItem('eg.printing.config', conf);
     }
+    // -----------
 
     // launch the print dialog then attach the resulting configuration
     // to the requested context, then store the final values.
@@ -263,9 +274,11 @@ angular.module('egCoreMod')
 
         // dispatch the print configuration request
         .then(function(config) {
+
+            // loaded remote config
             if (!config[context]) config[context] = {};
             config[context].printer = printer;
-            return service.dispatchRequest({
+            return service.attemptHatchDelivery({
                 key : 'no-op', 
                 action : 'print-config',
                 config : config[context]
@@ -292,82 +305,145 @@ angular.module('egCoreMod')
     }
 
     service.getPrinters = function() {
-        if (service.printers) 
+        if (service.printers) // cached printers
             return $q.when(service.printers);
 
-        return service.dispatchRequest({key : 'no-op', action : 'printers'})
-        .then(function(printers) {
-            service.printers = printers.sort(
-                function(a,b) {return a.name < b.name ? -1 : 1});
-            return service.printers;
-        });
+        return service.attemptHatchDelivery({action : 'printers'}).then(
+
+            // we have remote printers; sort by name and return
+            function(printers) {
+                service.printers = printers.sort(
+                    function(a,b) {return a.name < b.name ? -1 : 1});
+                return service.printers;
+            },
+
+            // remote call failed and there is no such thing as local
+            // printers; return empty set.
+            function() { return [] } 
+        );
     }
 
     // get the value for a stored item
     service.getItem = function(key) {
-        return service.dispatchRequest({key : key, action : 'get'});
+        return service.getRemoteItem(key)['catch'](
+            function(msg) {
+                return service.getLocalItem(msg.key);
+            }
+        );
+    }
+
+    service.getRemoteItem = function(key) {
+        return service.attemptHatchDelivery({
+            key : key,
+            action : 'get', 
+        });
+    }
+
+    service.getLocalItem = function(key) {
+        var val = $window.localStorage.getItem(key);
+        if (val == null) return;
+        return JSON.parse(val);
     }
 
-    // set the value for a stored or new item
     service.setItem = function(key, value) {
-        value = js2JSON(value); // all values stored as JSON text
-        return service.dispatchRequest(
-            {key : key, value : value, action : 'set'});
+        return service.setRemoteItem(key, value)['catch'](
+            function(msg) {
+                return service.setLocalItem(msg.key, msg.value);
+            }
+        );
+    }
+
+    // set the value for a stored or new item
+    service.setRemoteItem = function(key, value) {
+        value = JSON.stringify(value); // all values stored as JSON text
+        return service.attemptHatchDelivery({
+            key : key, 
+            value : value, 
+            action : 'set',
+        });
+    }
+
+    service.setLocalItem = function(key, value) {
+        $window.localStorage.setItem(key, JSON.stringify(value));
     }
 
     // appends the value to the existing item stored at key.
     // If not item is found at key, this behaves just like setItem()
     service.appendItem = function(key, value) {
-        return service.dispatchRequest(
-            {key : key, value : value, action : 'append'});
+        return service.appendRemoteItem(key, value)['catch'](
+            function(msg) {
+                return service.appendLocalItem(msg.key, msg.value);
+            }
+        );
+    }
+
+    service.appendRemoteItem = function(key, value) {
+        return service.attemptHatchDelivery({
+            key : key, 
+            value : value, 
+            action : 'append',
+        });
+    }
+
+    // assumes the appender and appendee are both strings
+    service.appendLocalItem = function(key, value) {
+        var item = service.getLocalItem(key);
+        if (item) {
+            if (typeof item != 'string') {
+                logger.warn("egPrintStore.appendLocalItem => "
+                    + "cannot append to a non-string item: " + key);
+                return;
+            }
+            value = item + value; // concatenate our value
+        }
+        service.setLocalitem(key, value);
     }
 
     // remove a stored item
     service.removeItem = function(key) {
-        return service.dispatchRequest({key : key, action : 'remove'});
+        return service.removeRemoteItem(key)['catch'](
+            function(msg) { 
+                return service.removeLocalItem(msg.key) 
+            }
+        );
+    }
+
+    service.removeRemoteItem = function(key) {
+        return service.attemptHatchDelivery({
+            key : key,
+            action : 'remove'
+        });
+    }
+
+    service.removeLocalItem = function(key) {
+        $window.localStorage.removeItem(key);
     }
 
     // if set, prefix limits the return set to keys starting with 'prefix'
     service.getKeys = function(prefix) {
-        return service.dispatchRequest({key : prefix, action : 'keys'});
+        return service.getRemoteKeys(prefix)['catch'](
+            function() { 
+                return service.getLocalKeys(prefix) 
+            }
+        );
     }
 
-    // get, set, remove, append, keys : via $window.localStorage
-    service.handleLocalStorageRequest = function(msg) {
-        console.log('service.handleLocalStorageRequest() ' + msg.action);
-        var key = msg.key;
-        var value = msg.value;
-        switch(msg.action) {
-            case 'keys':
-                var keys = [];
-                var idx = 0;
-                while ( (k = $window.localStorage.key(idx++)) !== null) {
-                    // key prefix match test
-                    if (key && k.substr(0, key.length) != key) continue; 
-                    keys.push(k);
-                }
-                msg.content = keys;
-                break;
-
-            case 'set':
-                $window.localStorage.setItem(key, value);
-                break;
-
-            case 'get':
-                msg.content = $window.localStorage.getItem(msg.key);
-                break;
-
-            case 'remove':
-                $window.localStorage.removeItem(msg.key);
-                break;
-
-            case 'append':
-                var item = $window.localStorage.getItem(key);
-                if (item) value = item + value;
-                $window.localStorage.setItem(key, value);
-                msg.content = value;
-                break;
+    service.getRemoteKeys = function(prefix) {
+        return service.attemptHatchDelivery({
+            key : prefix,
+            action : 'keys'
+        });
+    }
+
+    service.getLocalKeys = function(prefix) {
+        var keys = [];
+        var idx = 0;
+        while ( (k = $window.localStorage.key(idx++)) !== null) {
+            // key prefix match test
+            if (prefix && k.substr(0, prefix.length) != prefix) continue; 
+            keys.push(k);
         }
+        return keys;
     }
 
     return service;