From 12329947be0631546fc50c3bf71b841c04e970be Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Fri, 25 Apr 2014 10:52:19 -0400 Subject: [PATCH] browser staff : make accessing remote vs local actions possible Signed-off-by: Bill Erickson --- .../web/js/ui/default/staff/services/printstore.js | 320 +++++++++++++-------- 1 file changed, 198 insertions(+), 122 deletions(-) diff --git a/Open-ILS/web/js/ui/default/staff/services/printstore.js b/Open-ILS/web/js/ui/default/staff/services/printstore.js index c53a2f0437..495a610bd7 100644 --- a/Open-ILS/web/js/ui/default/staff/services/printstore.js +++ b/Open-ILS/web/js/ui/default/staff/services/printstore.js @@ -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; -- 2.11.0