[open-ils-commits] ***SPAM*** [GIT] Evergreen ILS branch master updated. 5d546cc4f21c9ea9431f4530af28fa5e6ad631c6

Evergreen Git git at git.evergreen-ils.org
Thu Sep 12 13:52:15 EDT 2013


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "Evergreen ILS".

The branch, master has been updated
       via  5d546cc4f21c9ea9431f4530af28fa5e6ad631c6 (commit)
       via  c31c7ab58ab6b99dc451b54301200c0fb58e1b92 (commit)
       via  8095a2efaa6ce9c264c531a46dc3983012686457 (commit)
       via  6de0fc1489856c74af2b0a5f55bb841aec74834d (commit)
       via  35bbeaea9a5dfc812c82c19061d96a9c514e2b7d (commit)
       via  62e7f75f9e68f0ee01dba56c3c5c2f841b7f8ae8 (commit)
       via  07b1be03657ff1a447b5e3e0f12a138bd2546791 (commit)
      from  d1d41d16c7ace2c097c0dbea8c0b72a3bc6a690c (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 5d546cc4f21c9ea9431f4530af28fa5e6ad631c6
Author: Mike Rylander <mrylander at gmail.com>
Date:   Thu Sep 12 13:48:47 2013 -0400

    Stamping P.V. SUPA upgrade scripts
    
    Signed-off-by: Mike Rylander <mrylander at gmail.com>

diff --git a/Open-ILS/src/sql/Pg/002.schema.config.sql b/Open-ILS/src/sql/Pg/002.schema.config.sql
index a8e7624..7649f6a 100644
--- a/Open-ILS/src/sql/Pg/002.schema.config.sql
+++ b/Open-ILS/src/sql/Pg/002.schema.config.sql
@@ -91,7 +91,7 @@ CREATE TRIGGER no_overlapping_deps
     BEFORE INSERT OR UPDATE ON config.db_patch_dependencies
     FOR EACH ROW EXECUTE PROCEDURE evergreen.array_overlap_check ('deprecates');
 
-INSERT INTO config.upgrade_log (version, applied_to) VALUES ('0825', :eg_version); -- phasefx/bshum
+INSERT INTO config.upgrade_log (version, applied_to) VALUES ('0826', :eg_version); -- phasefx/miker
 
 CREATE TABLE config.bib_source (
 	id		SERIAL	PRIMARY KEY,
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.server_addon_perms.sql b/Open-ILS/src/sql/Pg/upgrade/0826.data.server_addon_perms.sql
similarity index 83%
rename from Open-ILS/src/sql/Pg/upgrade/XXXX.data.server_addon_perms.sql
rename to Open-ILS/src/sql/Pg/upgrade/0826.data.server_addon_perms.sql
index efbd6a4..312c0c3 100644
--- a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.server_addon_perms.sql
+++ b/Open-ILS/src/sql/Pg/upgrade/0826.data.server_addon_perms.sql
@@ -1,6 +1,6 @@
 BEGIN;
 
-SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+SELECT evergreen.upgrade_deps_block_check('0826', :eg_version);
 
 INSERT INTO permission.perm_list ( id, code, description ) VALUES (
     551,

commit c31c7ab58ab6b99dc451b54301200c0fb58e1b92
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Tue Dec 11 20:02:38 2012 -0500

    P.V. SUPA GoodStuff integration
    
    This impliments a "Server Add-ons" module for integrating P.V. Supa's RFID
    product known as GoodStuff with the Evergreen staff client.
    
    To activate it, you should add the identifier "pv_supa_goodstuff" (without the
    quotes) to the list managed by the Admin->Workstation Administration->Server
    Add-ons menu action within the staff client.  You will need the
    ADMIN_SERVER_ADDON_FOR_WORKSTATION permission to do this.
    
    After doing this and clicking the Update Active Add-Ons button, the interface
    will refresh and show a GoodStuff tab in the Add-on Preferences section.  Within
    this tab you will have the option of specifying the hostname and port for the
    GoodStuff hardware. There is also an "Enabled" setting that needs to be checked.
    
    Currently three interfaces have been integrated:
    * Circulation -> Check In Items
    * Circulation -> Check Out Items (where you scan the patron barcode)
    * Circulation -> Check Out Items (where you scan the item barcodes)
    
    Each interface gets an RFID checkbox if the "Enabled" preference has been set,
    that can activate/deactivate the functionality on a per interface basis.  The
    checkbox states persist (i.e. are sticky).
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>
    Signed-off-by: Mike Rylander <mrylander at gmail.com>

diff --git a/Open-ILS/xul/staff_client/server/addon/pv_supa_goodstuff.js b/Open-ILS/xul/staff_client/server/addon/pv_supa_goodstuff.js
new file mode 100644
index 0000000..f2810b5
--- /dev/null
+++ b/Open-ILS/xul/staff_client/server/addon/pv_supa_goodstuff.js
@@ -0,0 +1,802 @@
+dump('entering addon/pv_supa.js\n');
+// vim:noet:sw=4:ts=4:
+
+/*
+    Usage example:
+
+    JSAN.use('addon.pv_supa_goodstuff');
+    var goodstuff = new addon.pv_supa_goodstuff();
+    goodstuff.onData(
+        function(data) {
+            alert('Received: ' + data);
+        },
+        'ACTIVATE'
+    );
+    goodstuff.request_items();
+*/
+
+if (typeof addon == 'undefined') addon = {};
+addon.pv_supa_goodstuff = function (params) {
+    var obj = this;
+    try {
+        dump('addon: pv_supa_goodstuff() constructor\n');
+
+        const Cc = Components.classes;
+        const Ci = Components.interfaces;
+        const prefs_Cc = '@mozilla.org/preferences-service;1';
+        this.prefs = Cc[prefs_Cc].getService(Ci['nsIPrefBranch']);
+
+        JSAN.use('OpenILS.data');
+        this.data = new OpenILS.data();
+        this.data.stash_retrieve();
+
+        JSAN.use('util.error');
+        this.error = new util.error();
+
+        this.active = false;
+        if (this.prefs.prefHasUserValue('oils.addon.pvsupa.goodstuff.enabled')){
+            this.active = this.prefs.getBoolPref(
+                'oils.addon.pvsupa.goodstuff.enabled'
+            );
+        }
+        if (this.active) {
+            dump('addon: pv_supa goodstuff enabled by preference\n');
+        } else {
+            dump('addon: pv_supa goodstuff not enabled by preference\n');
+        }
+
+        if (g) {
+            if (g.checkin && this.active) {
+                this.common_ui_init();
+                this.socket_init();
+                setTimeout(
+                    function() {
+                        obj.checkin_init();
+                    }, 1000
+                );
+            }
+            if (g.checkout && this.active) {
+                this.common_ui_init();
+                this.socket_init();
+                setTimeout(
+                    function() {
+                        obj.checkout_init();
+                    }, 1000
+                );
+            }
+            if (String(location.href).match('/patron/barcode_entry.xul') && this.active) {
+                this.common_ui_init();
+                this.socket_init();
+                setTimeout(
+                    function() {
+                        obj.patron_for_checkout_init();
+                    }, 1000
+                );
+            }
+            if (g.addons_ui) {
+                this.common_ui_init();
+                this.prefs_init();
+            }
+        }
+        return this;
+
+    } catch(E) {
+        dump('addon: Error in pv_supa_goodstuff(): ' + E + '\n');
+    }
+}
+
+addon.pv_supa_goodstuff.prototype = {
+    'common_ui_init' : function() {
+        dump('addon: pv_supa common_ui_init\n');
+        var obj = this;
+        try {
+            var mc = document.createElement('messagecatalog');
+            mc.setAttribute('id','addon_pvsupa_goodstuff_strings');
+            mc.setAttribute(
+                'src',
+                '/xul/server/locale/'+LOCALE+'/addon/pv_supa_goodstuff.properties'
+            );
+            var mc_parent = $('offlineStrings')
+                ? $('offlineStrings').parentNode
+                : document.getElementsByTagName('window').item(0)
+                || document.getElementsByTagName('html').item(0);
+            mc_parent.appendChild(mc);
+
+            // We don't really need CSS here, but as an example:
+            var sss = Cc['@mozilla.org/content/style-sheet-service;1']
+                .getService(Ci.nsIStyleSheetService);
+            var ios = Cc['@mozilla.org/network/io-service;1']
+                .getService(Ci.nsIIOService);
+            var uri = ios.newURI(
+                'oils://remote/xul/server/skin/addon/pv_supa_goodstuff.css',
+                null,
+                null
+            );
+            if(!sss.sheetRegistered(uri, sss.USER_SHEET)) {
+                sss.loadAndRegisterSheet(uri, sss.USER_SHEET);
+            }
+        } catch(E) {
+            dump('addon: pv_supa Error in common_ui_init(): ' + E + '\n');
+        }
+    },
+
+    'socket_init' : function() {
+        dump('addon: pv_supa socket_init on page ' + location.href + '\n');
+        var obj = this;
+        try {
+            if (! this.data.addon) {
+                this.data.addon = {};
+            }
+            if (! this.data.addon.pv_supa) {
+                this.data.addon.pv_supa = {};
+            }
+            if (! this.data.addon.pv_supa.goodstuff) {
+                this.data.addon.pv_supa.goodstuff = {};
+            }
+            if (! this.data.addon.pv_supa.goodstuff.message_log) {
+                this.data.addon.pv_supa.goodstuff.message_log = [];
+            }
+            if (this.data.addon.pv_supa.goodstuff.socket) {
+                    // don't know if we want to keep and re-use sockets
+                    // Previously I was test .socket.isAlive() and only
+                    // recreating if false
+                    dump('addon: pv_supa goodstuff destroying old socket\n');
+                    this.data.addon.pv_supa.goodstuff.socket.close();
+                    this.data.addon.pv_supa.goodstuff.socket = null;
+            }
+            if (! this.data.addon.pv_supa.goodstuff.socket) {
+                JSAN.use('util.socket');
+                this.data.addon.pv_supa.goodstuff.socket = new util.socket(
+
+                    this.prefs.prefHasUserValue('oils.addon.pvsupa.goodstuff.host')
+                    ? this.prefs.getCharPref('oils.addon.pvsupa.goodstuff.host')
+                    : '127.0.0.1',
+
+                    this.prefs.prefHasUserValue('oils.addon.pvsupa.goodstuff.port')
+                    ? this.prefs.getIntPref('oils.addon.pvsupa.goodstuff.port')
+                    : 5000 /* FIXME find out actual default port */
+                );
+                this.data.addon.pv_supa.goodstuff.socket.onStopRequest(
+                    function(request,context,result) {
+                        dump('addon: pv_supa goodstuff lost connection on page ' + location.href + '\n');
+                        obj.updateStatusBar('!lost connection\n');
+                        obj.data.addon.pv_supa.goodstuff.last_start_end_msg = null;
+                    }
+                );
+                dump('addon: pv_supa goodstuff socket opened\n');
+                this.updateStatusBar('!new connection\n');
+                obj.data.addon.pv_supa.goodstuff.last_start_end_msg = null;
+            } else {
+                dump('addon: pv_supa goodstuff socket already opened\n');
+            }
+            this.socket = this.data.addon.pv_supa.goodstuff.socket;
+            this.token = location.href + ' : ' + new Date();
+            this.data.addon.pv_supa.goodstuff.token = this.token;
+            var obj = this;
+            setTimeout(
+                function() {
+                    dump(
+                        'addon: pv_supa goodstuff socket\n\t' +
+                        'host: ' + obj.socket.host + '\n\t' +
+                        'port: ' + obj.socket.port + '\n\t' +
+                        'token: '+ obj.token
+                         + '\n'
+                    );
+                },
+                0
+            );
+        } catch(E) {
+            dump('addon: pv_supa Error in socket_init(): ' + E + '\n');
+        }
+    },
+    'updateStatusBar' : function(d) {
+        try {
+            var obj = this;
+            if (xulG && xulG.set_statusbar) {
+                if (obj.data.addon.pv_supa.goodstuff.message_log.length > 16) {
+                    obj.data.addon.pv_supa.goodstuff.message_log.shift();
+                }
+                obj.data.addon.pv_supa.goodstuff.message_log.push(d);
+                xulG.set_statusbar(
+                    5,
+                    'GoodStuff: ' + d,
+                    obj.data.addon.pv_supa.goodstuff.message_log.join(""),
+                    function(ev) {
+                        alert(
+                            obj.data.addon.pv_supa.goodstuff.message_log.join("")
+                        );
+                    }
+                );
+            }
+        } catch(E) {
+            dump('addon: pv_supa Error in updateStatusBar('+d+'): ' + E + '\n');
+        }
+    },
+    'onData' : function(f,security) {
+        dump('addon: setting pv_supa goodstuff onData callback, on page '
+            + location.href + ' with token ' + this.token + '\n');
+        var obj = this;
+        this.socket.dataCallback(
+            function(d) {
+                try {
+                    dump('addon: dataCallback func at page '
+                        + location.href + ' with token ' + obj.token + '\n');
+                    obj.updateStatusBar('>' + d);
+                    if (obj.token != obj.data.addon.pv_supa.goodstuff.token) {
+                        var e = 'addon error: pv supa: reading data out of turn\n';
+                        dump(e);
+                        obj.updateStatusBar('!' + e);
+                    }
+                    d= d.replace("\n","","g").replace("\r","","g").replace(" ","","g");
+                    var p = d.split("|");
+                    if (p.length == 1) {
+                        if (f) {
+                            f(p[0]); // hopefully a patron barcode
+                        }
+                    } else if (p[0] == 'START') {
+                        obj.data.addon.pv_supa.goodstuff.last_start_end_msg = p[0];
+                    } else if (p[0] == 'END') {
+                        obj.data.addon.pv_supa.goodstuff.last_start_end_msg = p[0];
+                    } else if (p[1] == 'NOK') {
+                        if (security) {
+                            var msg = $('addon_pvsupa_goodstuff_strings').getFormattedString(
+                                security == 'ACTIVATE'
+                                ? 'rfid.set_security_failure.prompt.message.activate_failure'
+                                : 'rfid.set_security_failure.prompt.message.deactivate_failure',
+                                [ p[0] ]
+                            );
+                            var choice = obj.error.yns_alert(
+                                msg,
+                                $('addon_pvsupa_goodstuff_strings').getString(
+                                    'rfid.set_security_failure.prompt.title'
+                                ),
+                                $('addon_pvsupa_goodstuff_strings').getString(
+                                    'rfid.set_security_failure.prompt.button.activate_security'
+                                ),
+                                $('addon_pvsupa_goodstuff_strings').getString(
+                                    'rfid.set_security_failure.prompt.button.deactivate_security'
+                                ),
+                                $('addon_pvsupa_goodstuff_strings').getString(
+                                    'rfid.set_security_failure.prompt.button.do_nothing_with_security'
+                                ),
+                                ''
+                            );
+                            if (choice == 0) {
+                                obj.write(p[0]+'|ACTIVATE\n');
+                            } else if (choice == 1) {
+                                obj.write(p[0]+'|DEACTIVATE\n');
+                            } else {
+                                obj.write(p[0]+'\n');
+                            }
+                        } else {
+                            dump('addon: unknown error\n');
+                        }
+                    } else if (p[1] == 'OK') {
+                        // ignore
+                    } else if (p[1].match('/')) {
+                        var counts = p[1].split('/');
+                        var read = counts[0];
+                        var set = counts[1];
+                        if (read != set) {
+                            var msg = $('addon_pvsupa_goodstuff_strings').getFormattedString(
+                                'rfid.partial_scan.prompt.message',
+                                [ read, set, p[0] ]
+                            );
+                            var choice = obj.error.yns_alert(
+                                msg,
+                                $('addon_pvsupa_goodstuff_strings').getString(
+                                    'rfid.partial_scan.prompt.title'
+                                ),
+                                $('addon_pvsupa_goodstuff_strings').getString(
+                                    'rfid.partial_scan.prompt.button.rescan'
+                                ),
+                                $('addon_pvsupa_goodstuff_strings').getString(
+                                    'rfid.partial_scan.prompt.button.skip'
+                                ),
+                                $('addon_pvsupa_goodstuff_strings').getString(
+                                    'rfid.partial_scan.prompt.button.proceed'
+                                ),
+                                ''
+                            );
+                            if (!choice || choice == 0) {
+                                obj.write(p[0]+'|REREAD\n');
+                            } else if (choice == 1) {
+                                obj.write(p[0]+'\n'); // do nothing, skip
+                            } else if (choice == 2) {
+                                if (f) {
+                                    f(p[0]); // hopefully an item barcode
+                                }
+                            }
+                        } else {
+                            if (f) {
+                                f(p[0]); // hopefully an item barcode
+                            }
+                        }
+                    } else {
+                        if (f) {
+                            f(p[0]); // no idea; shouldn't get here
+                        }
+                    }
+                } catch(E) {
+                    dump('addon: error in onData callback: ' + E + '\n');
+                }
+            }
+        );
+    },
+    'write' : function(s,ignore_token) {
+        dump('addon: write "' + s + '", on page ' + location.href + ' with token ' + this.token + '\n');
+        if ((this.token != this.data.addon.pv_supa.goodstuff.token) && !ignore_token) {
+            var e = 'addon error: pv supa: sending data out of turn\n';
+            dump(e);
+            this.updateStatusBar('!' + e);
+        }
+        if (!this.socket.socket.isAlive()) {
+            dump('addon error: pv supa: writing to not alive socket\n');
+        }
+        this.updateStatusBar('<' + s);
+        this.socket.write(s);
+    },
+    'request_items' : function() {
+        dump('addon: request_items on page ' + location.href + '\n');
+        if (this.data.addon.pv_supa.goodstuff.last_start_end_msg == 'START') {
+            this.write('END\n');
+        }
+        this.write('START|ITEM\n'); // we expect START|OK
+    },
+    'request_patrons' : function() {
+        dump('addon: request_patrons on page ' + location.href + '\n');
+        if (this.data.addon.pv_supa.goodstuff.last_start_end_msg == 'START') {
+            this.write('END\n');
+        }
+        this.write('START|PATRON\n'); // we expect START|OK
+    },
+    'end_session' : function() {
+        dump('addon: end_session on page ' + location.href + '\n');
+        if (this.token == this.data.addon.pv_supa.goodstuff.token) {
+            if (this.data.addon.pv_supa.goodstuff.last_start_end_msg == 'START') {
+                this.write('END\n');
+            }
+            this.data.addon.pv_supa.goodstuff.socket.close();
+            this.data.addon.pv_supa.goodstuff.socket = null;
+        }
+    },
+
+    'prefs_init' : function() {
+        dump('addon: pv_supa prefs_init\n');
+        var obj = this;
+
+        try {
+            if (! (g && g.addons_ui)) { return; }
+
+            const Cc = Components.classes;
+            const Ci = Components.interfaces;
+
+            function post_overlay() {
+                var tab = $('pv_supa_goodstuff_tab');
+                tab.setAttribute(
+                    'label',
+                    $('addon_pvsupa_goodstuff_strings').getString('prefs.tab.label')
+                );
+                tab.setAttribute(
+                    'accesskey',
+                    $('addon_pvsupa_goodstuff_strings').getString('prefs.tab.accesskey')
+                );
+
+                var enabled_label = $('pv_supa_goodstuff_enabled_label');
+                enabled_label.setAttribute(
+                    'value',
+                    $('addon_pvsupa_goodstuff_strings').getString('prefs.checkbox.label')
+                );
+                enabled_label.setAttribute(
+                    'accesskey',
+                    $('addon_pvsupa_goodstuff_strings').getString('prefs.checkbox.accesskey')
+                );
+
+                var host_label = $('pv_supa_goodstuff_hostname_label');
+                host_label.setAttribute(
+                    'value',
+                    $('addon_pvsupa_goodstuff_strings').getString('prefs.host.label')
+                );
+                host_label.setAttribute(
+                    'accesskey',
+                    $('addon_pvsupa_goodstuff_strings').getString('prefs.host.accesskey')
+                );
+
+                var port_label = $('pv_supa_goodstuff_port_label');
+                port_label.setAttribute(
+                    'value',
+                    $('addon_pvsupa_goodstuff_strings').getString('prefs.port.label')
+                );
+                port_label.setAttribute(
+                    'accesskey',
+                    $('addon_pvsupa_goodstuff_strings').getString('prefs.port.accesskey')
+                );
+
+                var save_btn = $('pv_supa_goodstuff_save_btn');
+                save_btn.setAttribute(
+                    'label',
+                    $('addon_pvsupa_goodstuff_strings').getString('prefs.update.label')
+                );
+                save_btn.setAttribute(
+                    'accesskey',
+                    $('addon_pvsupa_goodstuff_strings').getString('prefs.update.accesskey')
+                );
+                save_btn.addEventListener(
+                    'command',
+                    function() {
+                        obj.save_prefs();
+                    },
+                    false
+                );
+                obj.display_prefs();
+            }
+
+            function myObserver() { this.register(); }
+            myObserver.prototype = {
+                register: function() {
+                    var observerService = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
+                    observerService.addObserver(this, "xul-overlay-merged", false);
+                },
+                unregister: function() {
+                    var observerService = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
+                    observerService.removeObserver(this, "xul-overlay-merged");
+                },
+                observe: function(subject,topic,data) {
+                    dump('observe: <'+subject+','+topic+','+data+'>\n');
+                    // setTimeout is needed here for xulrunner 1.8
+                    setTimeout( function() { try { post_overlay(); } catch(E) { alert(E); } }, 0 );
+                }
+            }
+
+            var observer = new myObserver();
+            var url = '/xul/server/addon/pv_supa_goodstuff_config_overlay.xul';
+            document.loadOverlay(location.protocol + '//' + location.hostname + url,observer)
+
+        } catch(E) {
+            dump('addon: pv_supa Error in prefs_init(): ' + E + '\n');
+        }
+    },
+
+    'display_prefs' : function() {
+        var obj = this;
+        try {
+            $('pv_supa_goodstuff_enabled_cb').checked = obj.active;
+            $('pv_supa_goodstuff_hostname_tb').value =
+                obj.prefs.prefHasUserValue('oils.addon.pvsupa.goodstuff.host')
+                ? obj.prefs.getCharPref('oils.addon.pvsupa.goodstuff.host')
+                : '127.0.0.1';
+
+            $('pv_supa_goodstuff_port_tb').value =
+                obj.prefs.prefHasUserValue('oils.addon.pvsupa.goodstuff.port')
+                ? obj.prefs.getIntPref('oils.addon.pvsupa.goodstuff.port')
+                : 5000; /* FIXME find out actual default port */
+
+        } catch(E) {
+            dump('addon: pv_supa Error in display_prefs(): ' + E + '\n');
+        }
+    },
+
+    'save_prefs' : function() {
+        var obj = this;
+        try {
+            obj.prefs.setBoolPref(
+                'oils.addon.pvsupa.goodstuff.enabled',
+                $('pv_supa_goodstuff_enabled_cb').checked
+            );
+            obj.prefs.setCharPref(
+                'oils.addon.pvsupa.goodstuff.host',
+                $('pv_supa_goodstuff_hostname_tb').value
+            );
+            obj.prefs.setIntPref(
+                'oils.addon.pvsupa.goodstuff.port',
+                $('pv_supa_goodstuff_port_tb').value
+            );
+            location.href = location.href;
+        } catch(E) {
+            dump('addon: pv_supa Error in save_prefs(): ' + E + '\n');
+        }
+    },
+
+    'checkin_init' : function() {
+        dump('addon: pv_supa checkin_init\n');
+        var obj = this;
+
+        try {
+
+            if (! (g && g.checkin)) { return; }
+
+            function setOnData() {
+                obj.onData(
+                    function(barcode) {
+                        g.checkin.controller.view.checkin_barcode_entry_textbox.value = barcode;
+                        g.checkin.checkin();
+                        // unlike checkout, I don't really care whether the checkin
+                        // succeeds or not; I think we should activate security on
+                        // the item and move on.  Errored items are still listed in
+                        // the interface and can be handled separately.
+                        obj.write(barcode+'|ACTIVATE\n');
+                    },
+                    'ACTIVATE'
+                );
+            }
+            setOnData();
+
+            var spacer = $('pcii3s');
+            var rfid_cb = document.createElement('checkbox');
+            rfid_cb.setAttribute('id','addon_rfid_cb');
+            rfid_cb.setAttribute(
+                'label',
+                $('addon_pvsupa_goodstuff_strings').getString(
+                    'rfid.checkbox.label'
+                )
+            );
+            rfid_cb.setAttribute(
+                'accesskey',
+                $('addon_pvsupa_goodstuff_strings').getString(
+                    'rfid.checkbox.accesskey'
+                )
+            );
+            spacer.parentNode.insertBefore(rfid_cb,spacer);
+            if (obj.prefs.prefHasUserValue(
+                    'oils.addon.pvsupa.goodstuff.checkin.rfid_checkbox.checked'
+            )){
+                rfid_cb.checked = obj.prefs.getBoolPref(
+                    'oils.addon.pvsupa.goodstuff.checkin.rfid_checkbox.checked'
+                );
+            }
+
+            if (rfid_cb.checked) {
+                obj.request_items();
+            }
+
+            rfid_cb.addEventListener(
+                'command',
+                function(ev) {
+                    if (ev.target.checked) {
+                        obj.socket_init();
+                        setTimeout(
+                            function() {
+                                setOnData();
+                                obj.request_items();
+                            }, 1000
+                        );
+                    } else {
+                        obj.end_session();
+                    }
+                    obj.prefs.setBoolPref(
+                        'oils.addon.pvsupa.goodstuff.checkin.rfid_checkbox.checked',
+                        ev.target.checked
+                    );
+                }
+            );
+
+            window.addEventListener(
+                'unload',
+                function(ev) {
+                    obj.end_session();
+                },
+                false
+            );
+            window.addEventListener(
+                'tab_focus',
+                function(ev) {
+                    obj.socket_init();
+                    if (rfid_cb.checked) {
+                        setTimeout(
+                            function() {
+                                setOnData();
+                                obj.request_items();
+                            }, 1000
+                        );
+                    }
+                },
+                false
+            );
+
+        } catch(E) {
+            dump('addon: pv_supa Error in checkin_init(): ' + E + '\n');
+        }
+    },
+    'checkout_init' : function() {
+        dump('addon: pv_supa checkout_init\n');
+        var obj = this;
+
+        try {
+
+            if (! (g && g.checkout)) { return; }
+
+            function setOnData() {
+                obj.onData(
+                    function(barcode) {
+                        g.checkout.controller.view.checkout_barcode_entry_textbox.value = barcode;
+                        var pre_list_count = g.checkout.list.row_count.total;
+                        g.checkout.checkout({'barcode':barcode});
+                        var post_list_count = g.checkout.list.row_count.total;
+                        if (pre_list_count != post_list_count) {
+                            obj.write(barcode+'|DEACTIVATE\n'); // checkout success
+                        } else {
+                            obj.write(barcode+'|ACTIVATE\n'); // checkout failure
+                        }
+                    },
+                    'DEACTIVATE'
+                );
+            }
+            setOnData();
+
+            var spacer = $('pcii3s');
+            var rfid_cb = document.createElement('checkbox');
+            rfid_cb.setAttribute('id','addon_rfid_cb');
+            rfid_cb.setAttribute(
+                'label',
+                $('addon_pvsupa_goodstuff_strings').getString(
+                    'rfid.checkbox.label'
+                )
+            );
+            rfid_cb.setAttribute(
+                'accesskey',
+                $('addon_pvsupa_goodstuff_strings').getString(
+                    'rfid.checkbox.accesskey'
+                )
+            );
+            spacer.parentNode.insertBefore(rfid_cb,spacer);
+            if (obj.prefs.prefHasUserValue(
+                    'oils.addon.pvsupa.goodstuff.checkout.rfid_checkbox.checked'
+            )){
+                rfid_cb.checked = obj.prefs.getBoolPref(
+                    'oils.addon.pvsupa.goodstuff.checkout.rfid_checkbox.checked'
+                );
+            }
+
+            if (rfid_cb.checked) {
+                obj.request_items();
+            }
+
+            rfid_cb.addEventListener(
+                'command',
+                function(ev) {
+                    if (ev.target.checked) {
+                        obj.socket_init();
+                        setTimeout(
+                            function() {
+                                setOnData();
+                                obj.request_items();
+                            }, 1000
+                        );
+                    } else {
+                        obj.end_session();
+                    }
+                    obj.prefs.setBoolPref(
+                        'oils.addon.pvsupa.goodstuff.checkout.rfid_checkbox.checked',
+                        ev.target.checked
+                    );
+                }
+            );
+
+            window.addEventListener(
+                'unload',
+                function(ev) {
+                    obj.end_session();
+                },
+                false
+            );
+            window.addEventListener(
+                'tab_focus',
+                function(ev) {
+                    obj.socket_init();
+                    if (rfid_cb.checked) {
+                        setTimeout(
+                            function() {
+                                setOnData();
+                                obj.request_items();
+                            }, 1000
+                        );
+                    }
+                },
+                false
+            );
+
+        } catch(E) {
+            dump('addon: pv_supa Error in checkout_init(): ' + E + '\n');
+        }
+    },
+    'patron_for_checkout_init' : function() {
+        dump('addon: pv_supa patron_for_checkout_init\n');
+        var obj = this;
+
+        try {
+
+            if (! String(location.href).match('/patron/barcode_entry.xul')) { return; }
+
+            function setOnData() {
+                obj.onData(
+                    function(barcode) {
+                        obj.write(barcode+'\n');
+                        $('barcode_tb').value = barcode;
+                        window.submit();
+                    },
+                    'DEACTIVATE'
+                );
+            }
+            setOnData();
+
+            var hbox = $('barcode_tb').parentNode;
+            var rfid_cb = document.createElement('checkbox');
+            rfid_cb.setAttribute('id','addon_rfid_cb');
+            rfid_cb.setAttribute(
+                'label',
+                $('addon_pvsupa_goodstuff_strings').getString(
+                    'rfid.checkbox.label'
+                )
+            );
+            rfid_cb.setAttribute(
+                'accesskey',
+                $('addon_pvsupa_goodstuff_strings').getString(
+                    'rfid.checkbox.accesskey'
+                )
+            );
+            hbox.appendChild(rfid_cb);
+            if (obj.prefs.prefHasUserValue(
+                    'oils.addon.pvsupa.goodstuff.patron_for_checkout.rfid_checkbox.checked'
+            )){
+                rfid_cb.checked = obj.prefs.getBoolPref(
+                    'oils.addon.pvsupa.goodstuff.patron_for_checkout.rfid_checkbox.checked'
+                );
+            }
+
+            if (rfid_cb.checked) {
+                obj.request_patrons();
+            }
+
+            rfid_cb.addEventListener(
+                'command',
+                function(ev) {
+                    if (ev.target.checked) {
+                        obj.socket_init();
+                        setTimeout(
+                            function() {
+                                setOnData();
+                                obj.request_patrons();
+                            }, 1000
+                        );
+                    } else {
+                        obj.end_session();
+                    }
+                    obj.prefs.setBoolPref(
+                        'oils.addon.pvsupa.goodstuff.patron_for_checkout.rfid_checkbox.checked',
+                        ev.target.checked
+                    );
+                }
+            );
+
+            window.addEventListener(
+                'unload',
+                function(ev) {
+                    obj.end_session();
+                },
+                false
+            );
+            window.addEventListener(
+                'tab_focus',
+                function(ev) {
+                    obj.socket_init();
+                    if (rfid_cb.checked) {
+                        setTimeout(
+                            function() {
+                                setOnData();
+                                obj.request_patrons();
+                            }, 1000
+                        );
+                    }
+                },
+                false
+            );
+
+        } catch(E) {
+            dump('addon: pv_supa Error in patron_for_checkout_init(): ' + E + '\n');
+        }
+    }
+
+
+
+}
+
diff --git a/Open-ILS/xul/staff_client/server/addon/pv_supa_goodstuff_config_overlay.xul b/Open-ILS/xul/staff_client/server/addon/pv_supa_goodstuff_config_overlay.xul
new file mode 100644
index 0000000..f57e4a6
--- /dev/null
+++ b/Open-ILS/xul/staff_client/server/addon/pv_supa_goodstuff_config_overlay.xul
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<!DOCTYPE overlay PUBLIC "" ""[
+    <!--#include virtual="/opac/locale/${locale}/lang.dtd"-->
+]>
+<overlay id="pv_supa_goodstuff_config_overlay"
+    xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script>dump('loading addon/pv_supa_goodstuff_config_overlay.xul\n');</script>
+
+<tabs id="addonpref_tabs">
+    <tab id="pv_supa_goodstuff_tab" />
+</tabs>
+
+<tabpanels id="addonpref_tabpanels">
+    <tabpanel id="pv_supa_goodstuff_tabpanel">
+        <grid>
+            <columns>
+                <column/>
+                <column/>
+            </columns>
+            <rows>
+                <row>
+                    <label id="pv_supa_goodstuff_enabled_label"
+                        control="pv_supa_goodstuff_enabled_cb"/>
+                    <checkbox id="pv_supa_goodstuff_enabled_cb"/>
+                </row>
+                <row>
+                    <label id="pv_supa_goodstuff_hostname_label"
+                        control="pv_supa_goodstuff_hostname_tb"/>
+                    <textbox id="pv_supa_goodstuff_hostname_tb"/>
+                </row>
+                <row>
+                    <label id="pv_supa_goodstuff_port_label"
+                        control="pv_supa_goodstuff_port_tb"/>
+                    <textbox id="pv_supa_goodstuff_port_tb"/>
+                </row>
+                <row>
+                    <spacer/>
+                    <button id="pv_supa_goodstuff_save_btn" />
+                </row>
+            </rows>
+        </grid>
+    </tabpanel>
+</tabpanels>
+
+</overlay>
diff --git a/Open-ILS/xul/staff_client/server/addon/pv_supa_goodstuff_tests/pv_supa_goodstuff_test1.expect b/Open-ILS/xul/staff_client/server/addon/pv_supa_goodstuff_tests/pv_supa_goodstuff_test1.expect
new file mode 100755
index 0000000..edfeb60
--- /dev/null
+++ b/Open-ILS/xul/staff_client/server/addon/pv_supa_goodstuff_tests/pv_supa_goodstuff_test1.expect
@@ -0,0 +1,52 @@
+#!/usr/bin/expect -f
+# This test simulates interaction with GoodStuff hardware for the purpose
+# of RFID scanning a patron card.  The hardware is capable of fielding
+# multiple such cards in one pass, but the staff client will close the
+# connection after the first successful card.
+#
+# Prerequisites:
+#
+# Requires expect and netcat to be installed, and expects for a pristine
+# load of EG's stock test data to be installed.  In the staff client,
+# pv_supa_goodstuff should be listed in the Add-Ons list under Admin ->
+# Workstation Administration -> Server Add-ons.  In the GoodStuff
+# preferences section, Enabled should be checked, the IP/Hostname field
+# should point to the server running this test script, and the port
+# should be 5000.  Networking should be configured to allow the client
+# machine to reach port 5000 on this server.  If netcat is not installed
+# as /bin/nc, change the spawn line below as appropriate.
+#
+# Steps:
+#
+# 1) Ensure the staff client is configured as per the prerequisites, and
+#    clear all tabs in the staff client.
+# 2) Invoke this script.
+# 3) In the staff client, press F1 or invoke Circulation -> Checkout
+#    Items.  If the RFID checkbox is unchecked, check it.
+# 4) The script should run without errors, and the staff client should
+#    show an attempt at loading a patron with card "bad_card", and then
+#    the patron with card "99999376669" should load.  The script should
+#    end without errors.
+#
+set send_slow {1 .1}
+proc send {ignore arg} {
+    sleep .1
+    exp_send -s -- $arg
+}
+set timeout -1
+spawn "/bin/nc/" -l -p 5000
+match_max 100000
+expect "START|PATRON\r"
+send -- "START|OK\r"
+expect -exact "START|OK\r
+"
+send -- "bad_card\r"
+expect -exact "bad_card\r
+bad_card\r
+"
+send -- "99999376669\r"
+expect -exact "99999376669\r
+99999376669\r
+END\r
+"
+expect eof
diff --git a/Open-ILS/xul/staff_client/server/addon/pv_supa_goodstuff_tests/pv_supa_goodstuff_test2.expect b/Open-ILS/xul/staff_client/server/addon/pv_supa_goodstuff_tests/pv_supa_goodstuff_test2.expect
new file mode 100755
index 0000000..4115c14
--- /dev/null
+++ b/Open-ILS/xul/staff_client/server/addon/pv_supa_goodstuff_tests/pv_supa_goodstuff_test2.expect
@@ -0,0 +1,144 @@
+#!/usr/bin/expect -f
+# This test simulates interaction with GoodStuff hardware for the purpose
+# of RFID scanning items for checkout.
+#
+# Prerequisites:
+#
+# Requires expect and netcat to be installed, and expects for a pristine
+# load of EG's stock test data to be installed.  In the staff client,
+# pv_supa_goodstuff should be listed in the Add-Ons list under Admin ->
+# Workstation Administration -> Server Add-ons.  In the GoodStuff
+# preferences section, Enabled should be checked, the IP/Hostname field
+# should point to the server running this test script, and the port
+# should be 5000.  Networking should be configured to allow the client
+# machine to reach port 5000 on this server.  If netcat is not installed
+# as /bin/nc, change the spawn line below as appropriate.
+#
+# Steps:
+#
+# 1) Ensure the staff client is configured as per the prerequisites, and
+#    clear all tabs in the staff client.
+# 2) Invoke this script.
+# 3) In the staff client, press F4 or invoke Search -> Search for
+#    Patrons.  Enter Smith for the last name and submit the search.
+#    Retrieve the first patron, Cathy Smith.  If the RFID checkbox is
+#    unchecked, check it.
+# 4) The client will attempt to circulate CONC4000049, which is already
+#    checked out to another user.  Click "Cancel".
+# 5) The client will attempt to circulate CONC4000048, which is already
+#    checked out to another user.  Click "Normal Checkin then Checkout".
+# 6) The client will circulate CONC70000408, but the fake GoodStuff will
+#    fail to deactivate the security flag for the item.  Click
+#    "Deactivate Security".
+# 7) The client will circulate CONC70000401, but the fake GoodStuff will
+#    fail to deactivate the security flag for the item.  Click "Make No
+#    Change".
+# 8) The fake GoodStuff will report a problem with CONC70000394, that it
+#    expected two parts but could only detect one.  Click "Re-Scan Item".
+#    This item should circulate.
+# 9) The fake GoodStuff will report a problem with CONC70000387, that it
+#    expected two parts but could only detect one.  Click "Proceed with
+#    Item".  This item should circulate.
+# 10) Close the tab or click Done (and cancel any print job).  The script
+#     should end without errors.
+#
+# Note: added some gratuitous whitespace near the end with the socket
+#       messages; client should ignore whitespace
+#
+set send_slow {1 .1}
+proc send {ignore arg} {
+    sleep .1
+    exp_send -s -- $arg
+}
+set timeout -1
+spawn "/bin/nc" -l -p 5000
+match_max 100000
+expect "START|ITEM\r"
+send -- "START|OK\r"
+expect -exact "START|OK\r
+"
+send -- "CONC4000049"
+expect -exact "CONC4000049"
+send -- "|1/1\r"
+expect -exact "|1/1\r
+CONC4000049|ACTIVATE\r
+"
+send -- "CONC4000049"
+expect -exact "CONC4000049"
+send -- "|OK\r"
+expect -exact "|OK\r
+"
+send -- "CONC4000048"
+expect -exact "CONC4000048"
+send -- "|1/1\r"
+expect -exact "|1/1\r
+CONC4000048|DEACTIVATE\r
+"
+send -- "CONC4000048"
+expect -exact "CONC4000048"
+send -- "|OK\r"
+expect -exact "|OK\r
+"
+send -- "CONC70000408"
+expect -exact "CONC70000408"
+send -- "|1/1\r"
+expect -exact "|1/1\r
+CONC70000408|DEACTIVATE\r
+"
+send -- "CONC70000408"
+expect -exact "CONC70000408"
+send -- "|NOK\r"
+expect -exact "|NOK\r
+CONC70000408|DEACTIVATE\r
+"
+send -- "CONC70000408"
+expect -exact "CONC70000408"
+send -- "|OK\r"
+expect -exact "|OK\r
+"
+send -- "CONC70000401"
+expect -exact "CONC70000401"
+send -- "|1/1\r"
+expect -exact "|1/1\r
+CONC70000401|DEACTIVATE\r
+"
+send -- "CONC70000401"
+expect -exact "CONC70000401"
+send -- "|NOK\r"
+expect -exact "|NOK\r
+CONC70000401\r
+"
+send -- "CONC70000401"
+expect -exact "CONC70000401"
+send -- "|OK\r"
+expect -exact "|OK\r
+"
+send -- "CONC70000394"
+expect -exact "CONC70000394"
+send -- "|1/2\r"
+expect -exact "|1/2\r
+CONC70000394|REREAD\r
+"
+send -- "CONC70000394"
+expect -exact "CONC70000394"
+send -- "|2/2\r"
+expect -exact "|2/2\r
+CONC70000394|DEACTIVATE\r
+"
+send -- "CONC70000394"
+expect -exact "CONC70000394"
+send -- "|OK\r"
+expect -exact "|OK\r
+"
+send -- "CONC70000387 "
+expect -exact "CONC70000387 "
+send -- "| 1/ 2 \r"
+expect -exact "| 1/ 2 \r
+CONC70000387|DEACTIVATE\r
+"
+send -- "CONC70000387"
+expect -exact "CONC70000387"
+send -- "|OK\r"
+expect -exact "|OK\r
+END\r"
+expect eof
diff --git a/Open-ILS/xul/staff_client/server/addon/pv_supa_goodstuff_tests/pv_supa_goodstuff_test3.expect b/Open-ILS/xul/staff_client/server/addon/pv_supa_goodstuff_tests/pv_supa_goodstuff_test3.expect
new file mode 100755
index 0000000..a342034
--- /dev/null
+++ b/Open-ILS/xul/staff_client/server/addon/pv_supa_goodstuff_tests/pv_supa_goodstuff_test3.expect
@@ -0,0 +1,109 @@
+#!/usr/bin/expect -f
+# This test simulates interaction with GoodStuff hardware for the purpose
+# of RFID scanning items for checkout.
+#
+# Prerequisites:
+#
+# Requires expect and netcat to be installed, and expects for a pristine
+# load of EG's stock test data to be installed.  In the staff client,
+# pv_supa_goodstuff should be listed in the Add-Ons list under Admin ->
+# Workstation Administration -> Server Add-ons.  In the GoodStuff
+# preferences section, Enabled should be checked, the IP/Hostname field
+# should point to the server running this test script, and the port
+# should be 5000.  Networking should be configured to allow the client
+# machine to reach port 5000 on this server.  If netcat is not installed
+# as /bin/nc, change the spawn line below as appropriate.  The staff
+# client should registered to the BR1 branch and the stock admin user
+# should be used.
+#
+# Steps:
+#
+# 1) Ensure the staff client is configured as per the prerequisites, and
+#    clear all tabs in the staff client.
+# 2) Invoke this script.
+# 3) In the staff client, press F2 or invoke Circulation -> Check In Items.
+#    If the RFID checkbox is unchecked, check it.
+# 4) The client will transit item CONC90000480.  Click "Do Not Print".
+# 5) The client will fail to check in item "madeupitem".  Click "OK".
+# 6) The fake GoodStuff will report a problem scanning all the parts for
+#    item CONC40000120, and say that it read only 1 of 2 parts.  Click
+#    "Re-Scan Item".  The item will be checked in.
+# 7) The client will check-in item CONC40000121, but the fake GoodStuff
+#    will report a problem setting the item's security flag.  Click
+#    "Activate Security".  The problem will persist.  Click "Make No
+#    Change".
+# 8) Close the tab.  The script should end without errors.  This
+#    particular test may be ran again with identical results without
+#    needing to reset the test data.
+#
+set send_slow {1 .1}
+proc send {ignore arg} {
+    sleep .1
+    exp_send -s -- $arg
+}
+set timeout -1
+spawn "/bin/nc" -l -p 5000
+match_max 100000
+expect "START|ITEM\r"
+send -- "START|OK\r"
+expect -exact "START|OK\r
+"
+send -- "CONC90000480"
+expect -exact "CONC90000480"
+send -- "|1/1\r"
+expect -exact "|1/1\r
+CONC90000480|ACTIVATE\r
+"
+send -- "CONC90000480"
+expect -exact "CONC90000480"
+send -- "|OK\r"
+expect -exact "|OK\r
+"
+send -- "madeupitem|1/1\r"
+expect -exact "madeupitem|1/1\r
+madeupitem|ACTIVATE\r
+"
+send -- "madeupitem|OK\r"
+expect -exact "madeupitem|OK\r
+"
+send -- "CONC40000120"
+expect -exact "CONC40000120"
+send -- "|1/2\r"
+expect -exact "|1/2\r
+CONC40000120|REREAD\r
+"
+send -- "CONC40000120"
+expect -exact "CONC40000120"
+send -- "|2/2\r"
+expect -exact "|2/2\r
+CONC40000120|ACTIVATE\r
+"
+send -- "CONC40000120"
+expect -exact "CONC40000120"
+send -- "|OK\r"
+expect -exact "|OK\r
+"
+send -- "CONC40000121"
+expect -exact "CONC40000121"
+send -- "|1/1\r"
+expect -exact "|1/1\r
+CONC40000121|ACTIVATE\r
+"
+send -- "CONC40000121"
+expect -exact "CONC40000121"
+send -- "|NOK\r"
+expect -exact "|NOK\r
+CONC40000121|ACTIVATE\r
+"
+send -- "CONC40000121"
+expect -exact "CONC40000121"
+send -- "|NOK\r"
+expect -exact "|NOK\r
+CONC40000121\r
+"
+send -- "CONC40000121"
+expect -exact "CONC40000121"
+send -- "|OK\r"
+expect -exact "|OK\r
+END\r"
+expect eof
diff --git a/Open-ILS/xul/staff_client/server/locale/en-US/addon/pv_supa_goodstuff.properties b/Open-ILS/xul/staff_client/server/locale/en-US/addon/pv_supa_goodstuff.properties
new file mode 100644
index 0000000..6ef59e9
--- /dev/null
+++ b/Open-ILS/xul/staff_client/server/locale/en-US/addon/pv_supa_goodstuff.properties
@@ -0,0 +1,23 @@
+rfid.checkbox.label=RFID
+rfid.checkbox.accesskey=
+rfid.partial_scan.prompt.title=RFID Partial Scan
+rfid.partial_scan.prompt.message=Read %1$s of %2$s parts for item %3$s.
+rfid.partial_scan.prompt.button.rescan=Re-Scan Item
+rfid.partial_scan.prompt.button.skip=Skip to Next Item
+rfid.partial_scan.prompt.button.proceed=Proceed with Item
+rfid.set_security_failure.prompt.title=RFID Security Error
+rfid.set_security_failure.prompt.message.activate_failure=Failed to activate the security flag on item %1$s.
+rfid.set_security_failure.prompt.message.deactivate_failure=Failed to deactivate the security flag on item %1$s.
+rfid.set_security_failure.prompt.button.activate_security=Activate Security
+rfid.set_security_failure.prompt.button.deactivate_security=Deactivate Security
+rfid.set_security_failure.prompt.button.do_nothing_with_security=Make No Change
+prefs.tab.label=GoodStuff
+prefs.tab.accesskey=
+prefs.checkbox.label=Enabled
+prefs.checkbox.accesskey=
+prefs.host.label=IP/Hostname
+prefs.host.accesskey=
+prefs.port.label=Port
+prefs.port.accesskey=
+prefs.update.label=Update
+prefs.update.accesskey=
diff --git a/Open-ILS/xul/staff_client/server/skin/addon/pv_supa_goodstuff.css b/Open-ILS/xul/staff_client/server/skin/addon/pv_supa_goodstuff.css
new file mode 100644
index 0000000..d588001
--- /dev/null
+++ b/Open-ILS/xul/staff_client/server/skin/addon/pv_supa_goodstuff.css
@@ -0,0 +1 @@
+ at import url("pv_supa_goodstuff_custom.css");
diff --git a/docs/RELEASE_NOTES_NEXT/pv_supa_goodstuff.txt b/docs/RELEASE_NOTES_NEXT/pv_supa_goodstuff.txt
new file mode 100644
index 0000000..124da33
--- /dev/null
+++ b/docs/RELEASE_NOTES_NEXT/pv_supa_goodstuff.txt
@@ -0,0 +1,26 @@
+Upgrade Notes
+-------------
+P.V. SUPA GoodStuff Integration
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+There is now a "Server Add-ons" module for integrating P.V. Supa's RFID product
+known as GoodStuff with the Evergreen staff client.
+
+To activate it, you should add the identifier "pv_supa_goodstaff" (without the
+quotes) to the list managed by the Admin->Workstation Administration->Server
+Add-ons menu action within the staff client.  You will need the
+ADMIN_SERVER_ADDON_FOR_WORKSTATION permission to do this.
+
+After doing this and clicking the Update Active Add-Ons button, the interface
+will refresh and show a GoodStuff tab in the Add-on Preferences section.  Within
+this tab you will have the option of specifying the hostname and port for the
+Goodstaff hardware. There is also an "Enabled" setting that needs to be checked.
+
+Currently three interfaces have been integrated:
+* Circulation -> Check In Items
+* Circulation -> Check Out Items (where you scan the patron barcode)
+* Circulation -> Check Out Items (where you scan the item barcodes)
+
+Each interface gets an RFID checkbox if the "Enabled" preference has been set,
+that can activate/deactivate the functionality on a per interface basis.  The
+checkbox states persist (i.e. are sticky).
+

commit 8095a2efaa6ce9c264c531a46dc3983012686457
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Fri Dec 21 00:19:37 2012 -0500

    JSAN module autoloader for "add-ons"
    
    This adds a "Server Add-ons" menu entry under Admin -> Workstation
    Administration in the staff client.  Choosing this allows you to edit or set a
    list of identifiers that correspond to JSAN-style modules found in
    /server/addons/, and is meant mainly for activating 3rd party modules within the
    staff client on a per-workstation basis.  You need the
    ADMIN_SERVER_ADDON_FOR_WORKSTATION permission to use it.
    
    Configuration options for activated add-ons may also show up in this interface.
    
    More technical details:
    
    This Server Add-ons list is stored as a JSON array in the Mozilla preference
    'oils.addon.autoload.list'.
    
    We also add an addon.autoloader module that gets called in most XUL interfaces
    via the util_overlay file.  This autoloader will loop through the modules
    specified in 'oils.addon.autoload.list' and instantiate each, storing a
    reference to each of the newly created objects in a data structure in
    window.oils_autoloaded.
    
    So, as an example, let's say there is a module: /xul/server/addon/uber_scan.js
    And we have the identifier 'uber_scan' in our Server Add-ons list.  Then, on
    every page load of a XUL interface using the stock util overlay, we will
    effectively call:
    
        new addon.uber_scan();
    
    Causing the code in the module to execute.  It is up to the module to determine
    whether it needs to do anything within a given interface.
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>
    
    Conflicts [permission numbering]:
    	Open-ILS/src/sql/Pg/950.data.seed-values.sql

diff --git a/Open-ILS/src/sql/Pg/950.data.seed-values.sql b/Open-ILS/src/sql/Pg/950.data.seed-values.sql
index 8242492..1ceb535 100644
--- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql
+++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql
@@ -1607,7 +1607,9 @@ INSERT INTO permission.perm_list ( id, code, description ) VALUES
         'Allows the user to check-in long-overdue items, prompting ' ||
             'long-overdue check-in processing', 'ppl', 'code')), 
  ( 550, 'SET_CIRC_LONG_OVERDUE', oils_i18n_gettext(550,
-        'Allows the user to mark a circulation as long-overdue', 'ppl', 'code'))
+        'Allows the user to mark a circulation as long-overdue', 'ppl', 'code')),
+ ( 551, 'ADMIN_SERVER_ADDON_FOR_WORKSTATION', oils_i18n_gettext( 551,
+        'Allows a user to specify which Server Add-ons get invoked at the current workstation', 'ppl', 'description'))
 ;
 
 
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.server_addon_perms.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.server_addon_perms.sql
new file mode 100644
index 0000000..efbd6a4
--- /dev/null
+++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.server_addon_perms.sql
@@ -0,0 +1,17 @@
+BEGIN;
+
+SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+
+INSERT INTO permission.perm_list ( id, code, description ) VALUES (
+    551,
+    'ADMIN_SERVER_ADDON_FOR_WORKSTATION',
+    oils_i18n_gettext(
+        551,
+        'Allows a user to specify which Server Add-ons get invoked at the current workstation',
+        'ppl',
+        'description'
+    )
+);
+
+COMMIT;
+
diff --git a/Open-ILS/web/opac/locale/en-US/lang.dtd b/Open-ILS/web/opac/locale/en-US/lang.dtd
index 2249624..8a653fc 100644
--- a/Open-ILS/web/opac/locale/en-US/lang.dtd
+++ b/Open-ILS/web/opac/locale/en-US/lang.dtd
@@ -912,6 +912,7 @@
 <!ENTITY staff.main.menu.admin.client.hotkeys.clearworkstation.label "Clear Workstation Default">
 <!ENTITY staff.main.menu.admin.client.hotkeys.clearworkstation.accesskey "">
 <!ENTITY staff.main.menu.admin.client.search_prefs.label "Set Search Preferences">
+<!ENTITY staff.main.menu.admin.client.server_addon_ws_configure.label "Server Add-ons">
 <!ENTITY staff.main.menu.admin.client.toolbars "Toolbars">
 <!ENTITY staff.main.menu.admin.client.toolbars.current "Current">
 <!ENTITY staff.main.menu.admin.client.toolbars.config.label "Configure Toolbars">
diff --git a/Open-ILS/xul/staff_client/chrome/content/OpenILS/global_util.js b/Open-ILS/xul/staff_client/chrome/content/OpenILS/global_util.js
index fabe682..d1be186 100644
--- a/Open-ILS/xul/staff_client/chrome/content/OpenILS/global_util.js
+++ b/Open-ILS/xul/staff_client/chrome/content/OpenILS/global_util.js
@@ -643,3 +643,18 @@
             alert('Error in global_utils.js, widget_prompt(): ' + E);
         }
     }
+
+
+    window.addEventListener(
+        'load',
+        function(ev) {
+            try {
+                if (window.oils_autoloaded) { return; }
+                JSAN.use('addon.autoloader');
+                window.oils_autoloaded = new addon.autoloader();
+            } catch(E) {
+                dump('Error in global_util.js with addon.autoloader: ' + E + '\n');
+            }
+        },
+        false
+    );
diff --git a/Open-ILS/xul/staff_client/chrome/content/main/constants.js b/Open-ILS/xul/staff_client/chrome/content/main/constants.js
index 80748ca..95fa50f 100644
--- a/Open-ILS/xul/staff_client/chrome/content/main/constants.js
+++ b/Open-ILS/xul/staff_client/chrome/content/main/constants.js
@@ -517,5 +517,6 @@ var urls = {
     'SERIAL_PRINT_ROUTING_LIST_USERS' : 'oils://remote/eg/serial/print_routing_list_users',
     'XUL_SERIAL_BATCH_RECEIVE': 'oils://remote/xul/server/serial/batch_receive.xul',
     'EG_TRIGGER_EVENTS' : 'oils://remote/eg/actor/user/event_log',
-    'XUL_SEARCH_PREFS' : 'chrome://open_ils_staff_client/content/main/search_prefs.xul'
+    'XUL_SEARCH_PREFS' : 'chrome://open_ils_staff_client/content/main/search_prefs.xul',
+    'XUL_SERVER_ADDONS' : 'oils://remote/xul/server/addon/addons.xul'
 }
diff --git a/Open-ILS/xul/staff_client/chrome/content/main/menu.js b/Open-ILS/xul/staff_client/chrome/content/main/menu.js
index c461602..c98b224 100644
--- a/Open-ILS/xul/staff_client/chrome/content/main/menu.js
+++ b/Open-ILS/xul/staff_client/chrome/content/main/menu.js
@@ -1747,6 +1747,16 @@ main.menu.prototype = {
                     }
                 }
             ],
+            'cmd_server_addon_ws_configure' : [
+                ['oncommand'],
+                function() {
+                    try {
+                        obj.set_tab(obj.url_prefix('XUL_SERVER_ADDONS'),{'browser' : false});
+                    } catch(E) {
+                        alert(E);
+                    }
+                }
+            ]
         };
 
         JSAN.use('util.controller');
diff --git a/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul b/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
index 647ad70..2fba991 100644
--- a/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
+++ b/Open-ILS/xul/staff_client/chrome/content/main/menu_frame_menus.xul
@@ -360,6 +360,9 @@
              />
     <command id="cmd_copy_editor_copy_location_first_toggle" />
     <command id="cmd_search_prefs" />
+    <command id="cmd_server_addon_ws_configure"
+             perm="ADMIN_SERVER_ADDON_FOR_WORKSTATION"
+            />
 </commandset>
 
 <!-- The File menu on the main menu -->
@@ -529,6 +532,7 @@
                 <menuitem label="&staff.main.menu.admin.template_edit.label;" accesskey="&staff.main.menu.admin.template_edit.accesskey;" command="cmd_print_list_template_edit"/>
                 <menuitem label="&staff.server.admin.index.fonts_and_sounds;" command="cmd_local_admin_fonts_and_sounds"/>
                 <menuitem label="&staff.main.menu.admin.client.search_prefs.label;" command="cmd_search_prefs"/>
+                <menuitem label="&staff.main.menu.admin.client.server_addon_ws_configure.label;" command="cmd_server_addon_ws_configure"/>
                 <menuitem type="checkbox" label="&staff.main.menu.admin.client.copy_editor.copy_location.label;" command="cmd_copy_editor_copy_location_first_toggle"/>
                 <menu id="main.menu.admin.client.hotkeys" label="&staff.main.menu.admin.client.hotkeys;">
                     <menupopup id="main.menu.admin.client.hotkeys.popup">
diff --git a/Open-ILS/xul/staff_client/server/addon/addons.js b/Open-ILS/xul/staff_client/server/addon/addons.js
new file mode 100644
index 0000000..3824c0f
--- /dev/null
+++ b/Open-ILS/xul/staff_client/server/addon/addons.js
@@ -0,0 +1,83 @@
+var error;
+var g = { 'addons_ui' : true };
+var prefs;
+
+function my_init() {
+    try {
+        if (typeof JSAN == 'undefined') {
+            throw( "The JSAN library object is missing."); }
+        JSAN.errorLevel = "die"; // none, warn, or die
+        JSAN.addRepository('/xul/server/');
+        JSAN.use('util.error'); error = new util.error();
+        error.sdump('D_TRACE','my_init() for addon/addon.xul');
+
+        if (typeof window.xulG == 'object'
+            && typeof window.xulG.set_tab_name == 'function') {
+            try {
+                window.xulG.set_tab_name(
+                    $('addonStrings').getString('addons.tab.label')
+                );
+            } catch(E) {
+                alert(E);
+            }
+        }
+
+        const Cc = Components.classes;
+        const Ci = Components.interfaces;
+        const prefs_Cc = '@mozilla.org/preferences-service;1';
+        prefs = Cc[prefs_Cc].getService(Ci['nsIPrefBranch']);
+
+        var addons = JSON2js(
+            pref.prefHasUserValue('oils.addon.autoload.list')
+            ? pref.getCharPref('oils.addon.autoload.list')
+            : '[]'
+        );
+
+        $('addonlist_tb').value = addons.join("\n");
+
+        $('addonlist_desc').textContent = $('addonStrings').getString(
+            'addons.list.desc');
+        // Why messagecat instead of lang.dtd here? Mostly as an example for add-ons
+        // that won't have access to lang.dtd
+
+        $('addonlist_caption').setAttribute('label',$('addonStrings').getString(
+            'addons.list.caption'));
+
+        $('addonlist_save_btn').setAttribute(
+            'label', $('addonStrings').getString(
+                'addons.list.update_btn.label'));
+
+        $('addonlist_save_btn').setAttribute(
+            'accesskey', $('addonStrings').getString(
+                'addons.list.update_btn.accesskey'));
+
+        $('addonpref_caption').setAttribute('label',$('addonStrings').getString(
+            'addons.pref.caption'));
+
+    } catch(E) {
+        alert('Error in addons.js, my_init(): ' + E);
+    }
+}
+
+function update() {
+    try {
+        JSAN.use('util.functional');
+        var addon_string = $('addonlist_tb').value.replace(' ','','g');
+        var addons = util.functional.filter_list(
+            addon_string.split("\n"),
+            function(s) {
+                return s != ''; // filtering out empty lines
+            }
+        );
+
+        pref.setCharPref(
+            'oils.addon.autoload.list',
+            js2JSON(addons)
+        );
+
+        location.href = location.href;
+
+    } catch(E) {
+        alert('Error in addons.js, update(): ' + E);
+    }
+}
diff --git a/Open-ILS/xul/staff_client/server/addon/addons.xul b/Open-ILS/xul/staff_client/server/addon/addons.xul
new file mode 100644
index 0000000..8e8e5ca
--- /dev/null
+++ b/Open-ILS/xul/staff_client/server/addon/addons.xul
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<!-- Application: Evergreen Staff Client -->
+<!-- Screen: Example Template for remote xul -->
+
+<!-- /////////////////////////////////////////////////////////////////////// -->
+<!-- STYLESHEETS -->
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="/xul/server/skin/global.css" type="text/css"?>
+<?xml-stylesheet href="/xul/server/skin/addon/addons.css" type="text/css"?>
+
+<!-- /////////////////////////////////////////////////////////////////////// -->
+<!-- LOCALIZATION -->
+<!DOCTYPE window PUBLIC "" ""[
+    <!--#include virtual="/opac/locale/${locale}/lang.dtd"-->
+]>
+
+<!-- /////////////////////////////////////////////////////////////////////// -->
+<!-- OVERLAYS -->
+<?xul-overlay href="/xul/server/OpenILS/util_overlay.xul"?>
+
+<window id="addon_win"
+    onload="try { my_init(); font_helper(); persist_helper(); } catch(E) { alert(E); }"
+    xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+    <!-- /////////////////////////////////////////////////////////////////// -->
+    <!-- BEHAVIOR -->
+    <script type="text/javascript">
+        var myPackageDir = 'open_ils_staff_client'; var IAMXUL = true;
+    </script>
+    <scripts id="openils_util_scripts"/>
+
+    <script type="text/javascript" src="/xul/server/main/JSAN.js"/>
+    <script type="text/javascript" src="addons.js"/>
+
+    <messagecatalog id="addonStrings"
+        src="/xul/server/locale/<!--#echo var='locale'-->/addon/addons.properties" />
+
+    <vbox id="main" flex="1">
+        <groupbox id="addonlist_groupbox" flex="1">
+            <caption id="addonlist_caption" />
+            <description id="addonlist_desc"/>
+            <textbox id="addonlist_tb" multiline="true" flex="1"/>
+            <button id="addonlist_save_btn" oncommand="update()"/>
+        </groupbox>
+        <groupbox id="addonpref_groupbox" flex="1">
+            <caption id="addonpref_caption" />
+            <tabbox id="addonpref_tabbox" flex="1">
+                <tabs id="addonpref_tabs" />
+                <tabpanels id="addonpref_tabpanels" flex="1" />
+            </tabbox>
+        </groupbox>
+    </vbox>
+
+</window>
+
diff --git a/Open-ILS/xul/staff_client/server/addon/autoloader.js b/Open-ILS/xul/staff_client/server/addon/autoloader.js
new file mode 100644
index 0000000..1cc8f80
--- /dev/null
+++ b/Open-ILS/xul/staff_client/server/addon/autoloader.js
@@ -0,0 +1,78 @@
+dump('entering addon/autoloader.js\n');
+// vim:noet:sw=4:ts=4:
+
+/*
+    Usage example:
+
+    JSAN.use('addon.autoloader');
+    var autoloader = new addon.autoloader({'pref':'oils.addon.autoload.list'});
+    autoloader.list();
+    autoloader.objects();
+*/
+
+if (typeof addon == 'undefined') addon = {};
+addon.autoloader = function (params) {
+    try {
+        dump('addon: autoloader() constructor at ' + location.href + '\n');
+        if (typeof params == 'undefined') {
+            params = { 'pref' : 'oils.addon.autoload.list' };
+        }
+
+        const Cc = Components.classes;
+        const Ci = Components.interfaces;
+        const prefs_Cc = '@mozilla.org/preferences-service;1';
+        this.prefs = Cc[prefs_Cc].getService(Ci['nsIPrefBranch']);
+
+        this._list = this.list(params);
+
+        this._hash = this.load( this._list, params );
+
+        return this;
+
+    } catch(E) {
+        dump('addon: Error in autoloader(): ' + E + '\n');
+    }
+}
+
+addon.autoloader.prototype = {
+    'list' : function(params) {
+        var list = [];
+        if (typeof params == 'undefined') {
+            list = this._list;
+        }
+        if (params.pref) {
+            if (this.prefs.prefHasUserValue(params.pref)) {
+                list = list.concat(
+                    JSON2js(
+                        this.prefs.getCharPref(
+                            params.pref
+                        )
+                    )
+                );
+            }
+        }
+        if (params.list) {
+            list = list.concat( params.list );
+        }
+        return list;
+    },
+    'objects' : function() {
+        return this._hash;
+    },
+    'load' : function(list,params) {
+        dump('addon: autloader load()\n');
+        var objs = {};
+        for (var i = 0; i < list.length; i++) {
+            try {
+                dump('addon: autloader load() -> ' + list[i] + '\n');
+                JSAN.use('addon.'+list[i]);
+                objs[list[i]] = new addon[list[i]](params);
+            } catch(E) {
+                dump('addon: autloader load() -> ' + list[i] + ' error: ' + E + '\n');
+                objs[list[i]] = function(e){return e;}(E);
+            }
+        }
+        return objs;
+    }
+}
+
diff --git a/Open-ILS/xul/staff_client/server/locale/en-US/addon/addons.properties b/Open-ILS/xul/staff_client/server/locale/en-US/addon/addons.properties
new file mode 100644
index 0000000..35d3e71
--- /dev/null
+++ b/Open-ILS/xul/staff_client/server/locale/en-US/addon/addons.properties
@@ -0,0 +1,6 @@
+addons.tab.label=Server Add-ons
+addons.list.caption=Active Server Add-Ons
+addons.list.desc=Specify one add-on identifier per line
+addons.list.update_btn.label=Update Active Add-Ons
+addons.list.update_btn.accesskey=U
+addons.pref.caption=Add-on Preferences
diff --git a/docs/RELEASE_NOTES_NEXT/xul_server_addons.txt b/docs/RELEASE_NOTES_NEXT/xul_server_addons.txt
new file mode 100644
index 0000000..dd303ed
--- /dev/null
+++ b/docs/RELEASE_NOTES_NEXT/xul_server_addons.txt
@@ -0,0 +1,12 @@
+Upgrade Notes
+-------------
+Server Add-ons
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+This adds a "Server Add-ons" menu entry under Admin -> Workstation
+Administration in the staff client.  Choosing this allows you to edit or set a
+list of identifiers that correspond to JSAN-style modules found in
+/server/addons/, and is meant mainly for activating 3rd party modules within the
+staff client on a per-workstation basis.  You need the
+ADMIN_SERVER_ADDON_FOR_WORKSTATION permission to use it.
+
+Configuration options for activated add-ons may also show up in this interface.

commit 6de0fc1489856c74af2b0a5f55bb841aec74834d
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Mon Dec 10 06:30:33 2012 -0500

    internal: JSAN socket library
    
    Leverages Mozilla's nsISocketTransportService, nsIScriptableInputStream, and
    nsIInputStreamPump XPCOM, and the NetUtils javascript module.
    
        Usage example:
    
        Install netcat on a server and as root do:  nc -l -p 5000
    
        Then, in the staff client, load Admin -> For Developers -> Javascript Shell
    
        Enter:
    
        JSAN.use('util.socket');
        var s = new util.socket('server hostname or IP address here', 5000);
        s.write('hello\n');
    
        On the server, reply with world<enter>
    
        Back in the javascript shell, use
    
        s.read();
    
    This is geared to help with implementing future functionality, but shouldn't
    result in any end-user visible changes by itself.
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>
    Signed-off-by: Mike Rylander <mrylander at gmail.com>

diff --git a/Open-ILS/xul/staff_client/chrome/content/util/socket.js b/Open-ILS/xul/staff_client/chrome/content/util/socket.js
new file mode 100644
index 0000000..9dc875e
--- /dev/null
+++ b/Open-ILS/xul/staff_client/chrome/content/util/socket.js
@@ -0,0 +1,157 @@
+dump('entering util/socket.js\n');
+// vim:noet:sw=4:ts=4:
+
+/*
+    Usage example:
+
+    Install netcat on a server and as root do:  nc -l -p 5000
+
+    Then, in the staff client, load Admin -> For Developers -> Javascript Shell
+
+    Enter:
+
+    JSAN.use('util.socket');
+    var s = new util.socket('server hostname or IP address here', 5000);
+    s.write('hello\n');
+
+    On the server, reply with world<enter>
+
+    Back in the javascript shell, use
+
+    s.read();
+
+*/
+
+if (typeof util == 'undefined') util = {};
+util.socket = function (host,port,listener) {
+
+    try {
+        if (!host && !port) {
+            throw('host = ' + host + '  port = ' + port);
+        }
+
+        this.host = host;
+        this.port = port;
+        if (listener) {
+            this.listener = listener;
+        } else {
+            this._create_listener = true;
+        }
+
+        this.init();
+
+    } catch(E) {
+        alert('error in util.socket constructor: ' + E);
+        throw(E);
+    }
+
+    return this;
+};
+
+util.socket.prototype = {
+    '_data' : '',
+    '_onStartRequest' : null,
+    'onStartRequest' : function(callback) {
+        this._onStartRequest = callback;
+    },
+    '_onDataAvailable' : null,
+    'onDataAvailable' : function(callback) {
+        this._onDataAvailable = callback;
+    },
+    '_onStopRequest' : null,
+    'onStopRequest' : function(callback) {
+        this._onStopRequest = callback;
+    },
+    '_reconnectOnStop' : false,
+    'dataCallback' : function(callback) {
+        this._dataCallback = callback;
+    },
+    'init' : function() {
+        const Cc = Components.classes;
+        const Ci = Components.interfaces;
+        const socket_Cc = "@mozilla.org/network/socket-transport-service;1";
+        var transportService = Cc[socket_Cc].getService(
+            Ci.nsISocketTransportService
+        );
+        this.socket = transportService.createTransport(
+            null,0,this.host,this.port,null);
+        this.outputStream = this.socket.openOutputStream(0,0,0);
+        this.rawInputStream = this.socket.openInputStream(0,0,0);
+        const istream_Cc = "@mozilla.org/scriptableinputstream;1";
+        this.inputStream = Cc[istream_Cc].createInstance(
+            Ci.nsIScriptableInputStream
+        ).init(
+            this.rawInputStream
+        );
+
+        if (this._create_listener) {
+            this.listener = this.generate_listener();
+        }
+        const pump_Cc = "@mozilla.org/network/input-stream-pump;1";
+        this.pump = Cc[pump_Cc].createInstance(Ci.nsIInputStreamPump);
+        this.pump.init(this.rawInputStream,-1,-1,0,0,true);
+        this.pump.asyncRead(this.listener,null);
+    },
+    'close' : function() {
+        dump('util.socket.close() on page ' + location.href + '\n');
+        try {
+            this.pump.cancel(true);
+            this.socket.close(true);
+        } catch(E) {
+            dump('Error in util.socket.close(): ' + E + '\n');
+        }
+    },
+    'generate_listener' : function() {
+        var obj = this;
+        Components.utils.import("resource://gre/modules/NetUtil.jsm");
+        return {
+            onStartRequest : function(request,context) {
+                dump('util.socket.pump.onStartRequest on page ' + location.href + '\n');
+                if (obj._onStartRequest) {
+                    obj._onStartRequest(request,context);
+                }
+            },
+            onDataAvailable : function(request,context,stream,offset,count) {
+                dump('util.socket.pump.onDataAvailable on page ' + location.href + '\n');
+                if (obj._onDataAvailable) {
+                    dump('util.socket.pump.onDataAvailable using _onDataAvailable\n');
+                    obj._onDataAvailable(request,context,stream,offset,count);
+                }
+                var data = NetUtil.readInputStreamToString(stream,count);
+                dump(data + '\n');
+                if (obj._dataCallback) {
+                    dump('util.socket.pump.onDataAvailable using _dataCallback\n');
+                    obj._dataCallback(data);
+                } else {
+                    dump('util.socket.pump.onDataAvailable no _dataCallback to use\n');
+                    obj._data += data;
+                }
+            },
+            onStopRequest : function(request,context,result) {
+                dump('util.socket.pump.onStopRequest on page ' + location.href + '\n');
+                if (!Components.isSuccessCode(result)) {
+                    dump(result + '\n');
+                }
+                if (obj._onStopRequest) {
+                    obj._onStopRequest(request,context,result);
+                } else if (obj._reconnectOnStop) {
+                    // FIXME - cannot get this to work, but you
+                    // can overwrite the socket with a new socket
+                    // within  _onStopRequest
+                }
+            }
+        };
+    },
+    'write' : function(s) {
+        this.outputStream.write(s,s.length);
+    },
+    'read' : function(peek) {
+        var data = this._data;
+        if (!peek) {
+            this._data = '';
+        }
+        return data;
+    }
+}
+
+dump('exiting util/socket.js\n');

commit 35bbeaea9a5dfc812c82c19061d96a9c514e2b7d
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Thu Dec 20 17:17:24 2012 -0500

    internal: an alternative to default_focus
    
    Interfaces can now set event listeners on their window for 'tab_focus' instead
    of defining a single default_focus function.
    
    We also include a change to browser.js so that embedded interfaces also get
    their default_focus function called (and a tab_focus event sent).
    
    This is geared to help future functionality, but shouldn't produce any end-user
    visible changes by itself.
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>
    Signed-off-by: Mike Rylander <mrylander at gmail.com>

diff --git a/Open-ILS/xul/staff_client/chrome/content/main/menu.js b/Open-ILS/xul/staff_client/chrome/content/main/menu.js
index 805dcdb..c461602 100644
--- a/Open-ILS/xul/staff_client/chrome/content/main/menu.js
+++ b/Open-ILS/xul/staff_client/chrome/content/main/menu.js
@@ -2043,6 +2043,10 @@ main.menu.prototype = {
                         if (typeof cw.default_focus == 'function') {
                             cw.default_focus();
                         }
+                        // an alternative to the practice above
+                        var evt = cw.document.createEvent("Events");
+                        evt.initEvent( 'tab_focus', true, true );
+                        cw.window.dispatchEvent(evt);
                     }
                 } catch(E) {
                     obj.error.sdump('D_ERROR','init_tab_focus_handler: ' + js2JSON(E));
@@ -2506,6 +2510,10 @@ commands:
                                 if (typeof cw.default_focus == 'function') {
                                     cw.default_focus();
                                 }
+                                // an alternative to the practice above
+                                var evt = cw.document.createEvent("Events");
+                                evt.initEvent( 'tab_focus', true, true );
+                                cw.window.dispatchEvent(evt);
                             } catch(E) {
                                 obj.error.sdump('D_ERROR', 'main.menu, set_tab, onload: ' + E);
                             }
diff --git a/Open-ILS/xul/staff_client/chrome/content/util/browser.js b/Open-ILS/xul/staff_client/chrome/content/util/browser.js
index 20cb4dc..e452051 100644
--- a/Open-ILS/xul/staff_client/chrome/content/util/browser.js
+++ b/Open-ILS/xul/staff_client/chrome/content/util/browser.js
@@ -400,6 +400,17 @@ util.browser.prototype = {
         } else {
             dump(location.href + ': browser.js, updateNavButtons, xulG = ' + xulG + ' xulG.set_help_context = ' + xulG.set_help_context + '\n');
         }
+        try {
+            var cw = obj.get_content();
+            if (typeof cw.default_focus == 'function') {
+                cw.default_focus();
+            }
+            var evt = cw.document.createEvent("Events");
+            evt.initEvent( 'tab_focus', true, true );
+            cw.dispatchEvent(evt);
+        } catch(E) {
+            dump('Error in browser.js, calling cw.default_focus and cw.dispatchEvent: ' + E);
+        }
     },
 
     'buildProgressListener' : function() {

commit 62e7f75f9e68f0ee01dba56c3c5c2f841b7f8ae8
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Thu Dec 20 16:10:57 2012 -0500

    internal: default_focus not called
    
    Minor bug. Was being called in an else clause dependent on some help context
    logic for new tabs, but should be called for every new tab. Shouldn't result
    in any end-user visible change, but if interfaces wrapped in browser.js were
    to have a default_focus function, then this could come into play.
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>
    Signed-off-by: Mike Rylander <mrylander at gmail.com>

diff --git a/Open-ILS/xul/staff_client/chrome/content/main/menu.js b/Open-ILS/xul/staff_client/chrome/content/main/menu.js
index cc7a91a..805dcdb 100644
--- a/Open-ILS/xul/staff_client/chrome/content/main/menu.js
+++ b/Open-ILS/xul/staff_client/chrome/content/main/menu.js
@@ -2502,7 +2502,8 @@ commands:
                                         'src' : ''
                                     };
                                     obj.set_help_context(help_params);
-                                } else if (typeof cw.default_focus == 'function') {
+                                }
+                                if (typeof cw.default_focus == 'function') {
                                     cw.default_focus();
                                 }
                             } catch(E) {

commit 07b1be03657ff1a447b5e3e0f12a138bd2546791
Author: Jason Etheridge <jason at esilibrary.com>
Date:   Wed Dec 26 06:33:47 2012 -0500

    internal: allow checkout ui to set statusbar
    
    Minor bug fix and geared toward future functionality
    
    Signed-off-by: Jason Etheridge <jason at esilibrary.com>
    Signed-off-by: Mike Rylander <mrylander at gmail.com>

diff --git a/Open-ILS/xul/staff_client/server/patron/display.js b/Open-ILS/xul/staff_client/server/patron/display.js
index 5c10bab..fd46a3b 100644
--- a/Open-ILS/xul/staff_client/server/patron/display.js
+++ b/Open-ILS/xul/staff_client/server/patron/display.js
@@ -899,7 +899,8 @@ patron.display.prototype = {
                     },
                     'get_barcode' : xulG.get_barcode,
                     'get_barcode_and_settings' : xulG.get_barcode_and_settings,
-                    'url_prefix' : xulG.url_prefix
+                    'url_prefix' : xulG.url_prefix,
+                    'set_statusbar' : xulG.set_statusbar
                 }
             );
             obj.checkout_window = get_contentWindow(frame);

-----------------------------------------------------------------------

Summary of changes:
 Open-ILS/src/sql/Pg/002.schema.config.sql          |    2 +-
 Open-ILS/src/sql/Pg/950.data.seed-values.sql       |    4 +-
 .../Pg/upgrade/0826.data.server_addon_perms.sql    |   17 +
 Open-ILS/web/opac/locale/en-US/lang.dtd            |    1 +
 .../chrome/content/OpenILS/global_util.js          |   15 +
 .../staff_client/chrome/content/main/constants.js  |    3 +-
 .../xul/staff_client/chrome/content/main/menu.js   |   21 +-
 .../chrome/content/main/menu_frame_menus.xul       |    4 +
 .../staff_client/chrome/content/util/browser.js    |   11 +
 .../xul/staff_client/chrome/content/util/socket.js |  157 ++++
 Open-ILS/xul/staff_client/server/addon/addons.js   |   83 ++
 Open-ILS/xul/staff_client/server/addon/addons.xul  |   55 ++
 .../xul/staff_client/server/addon/autoloader.js    |   78 ++
 .../staff_client/server/addon/pv_supa_goodstuff.js |  802 ++++++++++++++++++++
 .../addon/pv_supa_goodstuff_config_overlay.xul     |   46 ++
 .../pv_supa_goodstuff_test1.expect                 |   52 ++
 .../pv_supa_goodstuff_test2.expect                 |  144 ++++
 .../pv_supa_goodstuff_test3.expect                 |  109 +++
 .../server/locale/en-US/addon/addons.properties    |    6 +
 .../en-US/addon/pv_supa_goodstuff.properties       |   23 +
 Open-ILS/xul/staff_client/server/patron/display.js |    3 +-
 .../server/skin/addon/pv_supa_goodstuff.css        |    1 +
 docs/RELEASE_NOTES_NEXT/pv_supa_goodstuff.txt      |   26 +
 docs/RELEASE_NOTES_NEXT/xul_server_addons.txt      |   12 +
 24 files changed, 1670 insertions(+), 5 deletions(-)
 create mode 100644 Open-ILS/src/sql/Pg/upgrade/0826.data.server_addon_perms.sql
 create mode 100644 Open-ILS/xul/staff_client/chrome/content/util/socket.js
 create mode 100644 Open-ILS/xul/staff_client/server/addon/addons.js
 create mode 100644 Open-ILS/xul/staff_client/server/addon/addons.xul
 create mode 100644 Open-ILS/xul/staff_client/server/addon/autoloader.js
 create mode 100644 Open-ILS/xul/staff_client/server/addon/pv_supa_goodstuff.js
 create mode 100644 Open-ILS/xul/staff_client/server/addon/pv_supa_goodstuff_config_overlay.xul
 create mode 100755 Open-ILS/xul/staff_client/server/addon/pv_supa_goodstuff_tests/pv_supa_goodstuff_test1.expect
 create mode 100755 Open-ILS/xul/staff_client/server/addon/pv_supa_goodstuff_tests/pv_supa_goodstuff_test2.expect
 create mode 100755 Open-ILS/xul/staff_client/server/addon/pv_supa_goodstuff_tests/pv_supa_goodstuff_test3.expect
 create mode 100644 Open-ILS/xul/staff_client/server/locale/en-US/addon/addons.properties
 create mode 100644 Open-ILS/xul/staff_client/server/locale/en-US/addon/pv_supa_goodstuff.properties
 create mode 100644 Open-ILS/xul/staff_client/server/skin/addon/pv_supa_goodstuff.css
 create mode 100644 docs/RELEASE_NOTES_NEXT/pv_supa_goodstuff.txt
 create mode 100644 docs/RELEASE_NOTES_NEXT/xul_server_addons.txt


hooks/post-receive
-- 
Evergreen ILS


More information about the open-ils-commits mailing list