Merge branch 'master' of git+ssh://yeti.esilibrary.com/home/evergreen/evergreen-equin...
authorMike Rylander <mrylander@gmail.com>
Mon, 2 May 2011 03:40:03 +0000 (23:40 -0400)
committerMike Rylander <mrylander@gmail.com>
Mon, 2 May 2011 03:40:03 +0000 (23:40 -0400)
16 files changed:
Open-ILS/src/extras/Makefile.install
Open-ILS/src/perlmods/lib/OpenILS/WWW/AddedContent/ContentCafe.pm
Open-ILS/src/perlmods/lib/OpenILS/WWW/AddedContent/OpenLibrary.pm
Open-ILS/src/sql/Pg/002.schema.config.sql
Open-ILS/src/sql/Pg/build-db.sh
Open-ILS/src/sql/Pg/make-db-patch.pl [new file with mode: 0755]
Open-ILS/src/sql/Pg/upgrade/0526.schema.upgrade-dep-tracking.sql [new file with mode: 0644]
Open-ILS/src/support-scripts/authority_control_fields.pl [deleted file]
Open-ILS/src/support-scripts/authority_control_fields.pl.in [new file with mode: 0755]
Open-ILS/src/support-scripts/marc_stream_importer.pl
Open-ILS/web/opac/skin/default/js/adv_global.js
Open-ILS/web/opac/skin/default/js/copy_details.js
Open-ILS/xul/staff_client/server/cat/copy_summary.xul
Open-ILS/xul/staff_client/server/patron/bill_details.js
README
configure.ac

index 9a34072..296b6e7 100644 (file)
@@ -4,17 +4,13 @@
 #
 # Makefile to install prerequisites for OpenSRF and Evergreen
 #
-# Currently supports Debian (lenny/squeeze), Ubuntu (hardy/lucid), and Fedora (13).
+# Currently supports Debian (squeeze), Ubuntu (lucid), and Fedora (14).
 # Working towards support of CentOS 5 / RHEL 5.
 # Installs Perl prereqs, libjs with Perl wrapper, libdbi, libdbi-drivers, and libyaz
 #
 # usage:
-#      make -f Makefile.install debian-lenny
-#      - or -
 #      make -f Makefile.install debian-squeeze
 #      - or -
-#      make -f Makefile.install ubuntu-hardy
-#      - or -
 #      make -f Makefile.install ubuntu-lucid
 #      - or -
 #      make -f Makefile.install fedora14
@@ -25,8 +21,8 @@
 #
 # Notes:
 #
-#      This makefile has been tested much more with Debian and Ubuntu than
-#      Fedora, CentOS, or RHEL.
+#      This makefile has been tested much more with Debian, Ubuntu, and
+#      Fedora than CentOS, or RHEL.
 #
 # ---------------------------------------------------------------------
  
@@ -155,7 +151,7 @@ CENTOS_PERL = \
        Net::XMPP \
        Net::Z3950::ZOOM
 
-FEDORA_13_RPMS = \
+FEDORA_RPMS = \
        aspell \
        aspell-en \
        libdbi \
@@ -183,7 +179,7 @@ FEDORA_13_RPMS = \
        perl-Text-CSV \
        perl-Text-CSV_XS \
        perl-XML-Writer \
-       postgresql-devel \
+       postgresql90-devel \
        readline-devel \
        tcp_wrappers-devel \
        wget \
@@ -192,33 +188,28 @@ FEDORA_13_RPMS = \
 # Note: B:O:AuthorizeNet 3.21 fails with https://rt.cpan.org/Public/Bug/Display.html?id=55172
 # Should be fixed in 3.22
 # MARC::Record 2.0.1 is required but only 2.0.0 is packaged
-FEDORA_13_CPAN = \
+FEDORA_CPAN = \
        Business::OnlinePayment \
        Business::OnlinePayment::AuthorizeNet \
        MARC::Record \
        UUID::Tiny
 
-PGSQL_84_RPMS = \
-       postgresql-8.4* \
-       postgresql-contrib-8.4* \
-       postgresql-devel-8.4* \
-       postgresql-plpe*-8.4* \
-       postgresql-server-8.4*
-
-PGSQL_CLIENT_DEBS_82 = \
-       postgresql-client
+PGSQL_90_RPMS = \
+       postgresql90 \
+       postgresql90-contrib \
+       postgresql90-devel \
+       postgresql90-libs \
+       postgresql90-plperl \
+       postgresql90-server
 
-PGSQL_CLIENT_DEBS_83 = \
+PGSQL_CLIENT_DEBS_90 = \
        postgresql-client
 
-PGSQL_CLIENT_DEBS_84 = \
-       postgresql-client-8.4
-
-PGSQL_SERVER_DEBS_84 = \
-       postgresql-8.4 \
-       postgresql-contrib-8.4 \
-       postgresql-plperl-8.4 \
-       postgresql-server-dev-8.4
+PGSQL_SERVER_DEBS_90 = \
+       postgresql-9.0 \
+       postgresql-contrib-9.0 \
+       postgresql-plperl-9.0 \
+       postgresql-server-dev-9.0
 
 DEB_APACHE_MODS = \
     expires\
@@ -271,19 +262,14 @@ centos: install_centos_pgsql centos_like
 rhel: install_redhat_pgsql centos_like
 centos_like: install_centos_rpms install_yaz install_cpan_marc install install_centos_perl create_ld_local install_cpan_safe install_cpan_force
 
-fedora13: install_fedora_13_rpms install_cpan install_cpan_fedora install_cpan_marc install_js_sm install_cpan_force
-fedora14: fedora13
+fedora14: install_fedora_rpms install_cpan install_cpan_fedora install_cpan_marc install_js_sm install_cpan_force
 
-debian-lenny: lenny generic_debian install_cpan_more install_cpan_safe
 debian-squeeze: squeeze generic_debian
-lenny: install_pgsql_client_debs_83 install_extra_debs
-squeeze: install_pgsql_client_debs_84  install_extra_debs_squeeze
+squeeze: install_pgsql_client_debs_90  install_extra_debs_squeeze
 generic_debian:  install_debs test_for_libdbi_pkg install debian_sys_config install_cpan_force
 
-ubuntu-hardy: hardy generic_ubuntu
 ubuntu-lucid: lucid generic_ubuntu
-hardy: install_pgsql_client_debs_82 install_yaz install_cpan_marc install_extra_encode
-lucid: install_pgsql_client_debs_84 install_extra_debs
+lucid: install_pgsql_client_debs_90 install_extra_debs
 generic_ubuntu: install_debs test_for_libdbi_pkg install debian_sys_config install_cpan_more install_cpan_safe install_cpan_force
 
 # - COMMON TARGETS ---------------------------------------------------------
@@ -311,7 +297,7 @@ install_cpan_safe:
 
 # Install the CPAN modules for Fedora 13
 install_cpan_fedora: 
-       for m in $(FEDORA_13_CPAN); do \
+       for m in $(FEDORA_CPAN); do \
                echo "force install $$m" | perl -MCPAN -e shell;\
        done
 
@@ -392,17 +378,11 @@ debian_sys_config:
 install_debs:
        $(APT_TOOL) install $(DEBS)
 
-install_pgsql_client_debs_84:
-       $(APT_TOOL) install $(PGSQL_CLIENT_DEBS_84)
-
-install_pgsql_server_debs_84:
-       $(APT_TOOL) install $(PGSQL_SERVER_DEBS_84)
-
-install_pgsql_client_debs_83:
-       $(APT_TOOL) install $(PGSQL_CLIENT_DEBS_83)
+install_pgsql_client_debs_90:
+       $(APT_TOOL) install $(PGSQL_CLIENT_DEBS_90)
 
-install_pgsql_client_debs_82:
-       $(APT_TOOL) install $(PGSQL_CLIENT_DEBS_82)
+install_pgsql_server_debs_90:
+       $(APT_TOOL) install $(PGSQL_SERVER_DEBS_90)
 
 # Install the debian-specific dependencies for more modern distros
 install_extra_debs_squeeze: install_extra_debs
@@ -418,11 +398,11 @@ install_extra_encode:
 # ------------------------------------------------------------------
 
 # FEDORA 13
-install_fedora_13_rpms:
+install_fedora_rpms:
        yum -y update
-       yum -y install $(FEDORA_13_RPMS)
+       yum -y install $(FEDORA_RPMS)
 
-install_fedora_13_pgsql_server:
+install_fedora_pgsql_server:
        yum -y install $(PGSQL_84_RPMS)
 
 # CENTOS
index c6e7f4f..9ecaebe 100644 (file)
@@ -206,6 +206,27 @@ sub summary_json {
         $self->fetch_content('AnnotationDetail', $key));
 }
 
+sub available_json {
+    my($self, $key) = @_;
+    my $xml = $self->fetch_content('AvailableContent', $key);
+    my $doc = XML::LibXML->new->parse_string($xml);
+
+    my @avail;
+    for my $node ($doc->findnodes('//*[text()="true"]')) {
+        push(@avail, 'summary') if $node->nodeName eq 'Annotation';
+        push(@avail, 'jacket') if $node->nodeName eq 'Jacket';
+        push(@avail, 'toc') if $node->nodeName eq 'TOC';
+        push(@avail, 'anotes') if $node->nodeName eq 'Biography';
+        push(@avail, 'excerpt') if $node->nodeName eq 'Excerpt';
+        push(@avail, 'reviews') if $node->nodeName eq 'Review';
+    }
+
+    return { 
+        content_type => 'text/plain', 
+        content => OpenSRF::Utils::JSON->perl2JSON(\@avail)
+    };
+}
+
 
 # --------------------------------------------------------------------------
 
index fab9ea9..2be38a0 100644 (file)
@@ -1,6 +1,6 @@
 # ---------------------------------------------------------------
 # Copyright (C) 2009 David Christensen <david.a.christensen@gmail.com>
-# Copyright (C) 2009 Dan Scott <dscott@laurentian.ca>
+# Copyright (C) 2009-2011 Dan Scott <dscott@laurentian.ca>
 #
 # This program is free software; you can redistribute it and/or
 # modify it under the terms of the GNU General Public License
@@ -30,7 +30,13 @@ my $AC = 'OpenILS::WWW::AddedContent';
 
 # These URLs are always the same for OpenLibrary, so there's no advantage to
 # pulling from opensrf.xml; we hardcode them here
-my $base_url = 'http://openlibrary.org/api/books?details=true&bibkeys=ISBN:';
+
+# jscmd=details is unstable but includes goodies such as Table of Contents
+my $base_url_details = 'http://openlibrary.org/api/books?format=json&jscmd=details&bibkeys=ISBN:';
+
+# jscmd=data is stable and contains links to ebooks, excerpts, etc
+my $base_url_data = 'http://openlibrary.org/api/books?format=json&jscmd=data&bibkeys=ISBN:';
+
 my $cover_base_url = 'http://covers.openlibrary.org/b/isbn/';
 
 sub new {
@@ -43,23 +49,120 @@ sub new {
 sub jacket_small {
     my( $self, $key ) = @_;
     return $self->send_img(
-        $self->fetch_cover_response('-S.jpg', $key));
+        $self->fetch_cover_response('small', $key));
 }
 
 sub jacket_medium {
     my( $self, $key ) = @_;
     return $self->send_img(
-        $self->fetch_cover_response('-M.jpg', $key));
+        $self->fetch_cover_response('medium', $key));
 
 }
 sub jacket_large {
     my( $self, $key ) = @_;
     return $self->send_img(
-        $self->fetch_cover_response('-L.jpg', $key));
+        $self->fetch_cover_response('large', $key));
 }
 
 # --------------------------------------------------------------------------
 
+sub ebooks_html {
+    my( $self, $key ) = @_;
+    my $book_data_json = $self->fetch_data_response($key)->content();
+
+    $logger->debug("$key: " . $book_data_json);
+
+    my $ebook_html;
+    
+    my $book_data = OpenSRF::Utils::JSON->JSON2perl($book_data_json);
+    my $book_key = (keys %$book_data)[0];
+
+    # We didn't find a matching book; short-circuit our response
+    if (!$book_key) {
+        $logger->debug("$key: no found book");
+        return 0;
+    }
+
+    my $ebooks_json = $book_data->{$book_key}->{ebooks};
+
+    # No ebooks are available for this book; short-circuit
+    if (!$ebooks_json or !scalar(@$ebooks_json)) {
+        $logger->debug("$key: no ebooks");
+        return 0;
+    }
+
+    # Check the availability of the ebooks
+    my $available = $ebooks_json->[0]->{'availability'} || '';
+    if (!$available) {
+        $logger->debug("$key: no available ebooks");
+        return 0;
+    }
+
+    # Build a basic list of available ebook types and their URLs
+    # ebooks appears to be an array containing one element - a hash
+
+    # First element of the hash is 'read_url' which is a URL to
+    # Internet Archive online reader
+    my $stream_url = $ebooks_json->[0]->{'read_url'} || '';
+    if ($stream_url) {
+        $ebook_html .= "<li class='ebook_stream'><a href='$stream_url'>Read online</a></li>\n";
+        $logger->debug("$key: stream URL = $stream_url");
+    }
+
+    my $ebook_formats = $ebooks_json->[0]->{'formats'} || '';
+    # Next elements are various ebook formats that are available
+    foreach my $ebook (keys %{$ebook_formats}) {
+        if ($ebook_formats->{$ebook} eq 'read_url') {
+            next;
+        }
+        $ebook_html .= "<li class='ebook_$ebook'><a href='" . 
+            $ebook_formats->{$ebook}->{'url'} . "'>" . uc($ebook) . "</a></li>\n";
+    }
+
+    $logger->debug("$key: $ebook_html");
+    $self->send_html("<ul class='ebooks'>$ebook_html</ul>");
+}
+
+sub excerpt_html {
+    my( $self, $key ) = @_;
+    my $book_details_json = $self->fetch_details_response($key)->content();
+
+    $logger->debug("$key: $book_details_json");
+
+    my $excerpt_html;
+    
+    my $book_details = OpenSRF::Utils::JSON->JSON2perl($book_details_json);
+    my $book_key = (keys %$book_details)[0];
+
+    # We didn't find a matching book; short-circuit our response
+    if (!$book_key) {
+        $logger->debug("$key: no found book");
+        return 0;
+    }
+
+    my $first_sentence = $book_details->{$book_key}->{first_sentence};
+    if ($first_sentence) {
+        $excerpt_html .= "<div class='sentence1'>$first_sentence</div>\n";
+    }
+
+    my $excerpts_json = $book_details->{$book_key}->{excerpts};
+    if ($excerpts_json && scalar(@$excerpts_json)) {
+        # Load up excerpt text with comments in tooltip
+        foreach my $excerpt (@$excerpts_json) {
+            my $text = $excerpt->{text};
+            my $cmnt = $excerpt->{comment};
+            $excerpt_html .= "<div class='ac_excerpt' title='$text'>$cmnt</div>\n";
+        }
+    }
+
+    if (!$excerpt_html) {
+        return 0;
+    }
+
+    $logger->debug("$key: $excerpt_html");
+    $self->send_html("<div class='ac_excerpts'>$excerpt_html</div>");
+}
+
 =head1
 
 OpenLibrary returns a JSON hash of zero or more book responses matching our
@@ -74,12 +177,7 @@ HTML table.
 
 sub toc_html {
     my( $self, $key ) = @_;
-    my $book_details_json = $self->fetch_response($key)->content();
-
-
-    # Trim the "var _OlBookInfo = " declaration that makes this
-    # invalid JSON
-    $book_details_json =~ s/^.+?({.*?});$/$1/s;
+    my $book_details_json = $self->fetch_details_response($key)->content();
 
     $logger->debug("$key: " . $book_details_json);
 
@@ -127,7 +225,7 @@ sub toc_html {
 sub toc_json {
     my( $self, $key ) = @_;
     my $toc = $self->send_json(
-        $self->fetch_response($key)
+        $self->fetch_details_response($key)
     );
 }
 
@@ -167,9 +265,16 @@ sub send_html {
 }
 
 # returns the HTTP response object from the URL fetch
-sub fetch_response {
+sub fetch_data_response {
     my( $self, $key ) = @_;
-    my $url = $base_url . "$key";
+    my $url = $base_url_data . "$key";
+    my $response = $AC->get_url($url);
+    return $response;
+}
+# returns the HTTP response object from the URL fetch
+sub fetch_details_response {
+    my( $self, $key ) = @_;
+    my $url = $base_url_details . "$key";
     my $response = $AC->get_url($url);
     return $response;
 }
@@ -177,8 +282,26 @@ sub fetch_response {
 # returns the HTTP response object from the URL fetch
 sub fetch_cover_response {
     my( $self, $size, $key ) = @_;
-    my $url = $cover_base_url . "$key$size";
-    return $AC->get_url($url);
+
+    my $response = $self->fetch_data_response($key)->content();
+
+    my $book_data = OpenSRF::Utils::JSON->JSON2perl($response);
+    my $book_key = (keys %$book_data)[0];
+
+    # We didn't find a matching book; short-circuit our response
+    if (!$book_key) {
+        $logger->debug("$key: no found book");
+        return 0;
+    }
+
+    my $covers_json = $book_data->{$book_key}->{cover};
+    if (!$covers_json) {
+        $logger->debug("$key: no covers for this book");
+        return 0;
+    }
+
+    $logger->debug("$key: " . $covers_json->{$size});
+    return $AC->get_url($covers_json->{$size}) || 0;
 }
 
 
index 0ca0d89..91e46e4 100644 (file)
@@ -54,10 +54,39 @@ ALTER TABLE config.global_flag ADD PRIMARY KEY (name);
 
 CREATE TABLE config.upgrade_log (
     version         TEXT    PRIMARY KEY,
-    install_date    TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
+    install_date    TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
+    applied_to      TEXT
 );
 
-INSERT INTO config.upgrade_log (version) VALUES ('0524'); -- phasefx
+CREATE TABLE config.db_patch_dependencies (
+  db_patch      TEXT PRIMARY KEY,
+  supersedes    TEXT[],
+  deprecates    TEXT[]
+);
+
+CREATE OR REPLACE FUNCTION evergreen.array_overlap_check (/* field */) RETURNS TRIGGER AS $$
+DECLARE
+    fld     TEXT;
+    cnt     INT;
+BEGIN
+    fld := TG_ARGV[1];
+    EXECUTE 'SELECT COUNT(*) FROM '|| TG_TABLE_SCHEMA ||'.'|| TG_TABLE_NAME ||' WHERE '|| fld ||' && ($1).'|| fld INTO cnt USING NEW;
+    IF cnt > 0 THEN
+        RAISE EXCEPTION 'Cannot insert duplicate array into field % of table %', fld, TG_TABLE_SCHEMA ||'.'|| TG_TABLE_NAME;
+    END IF;
+    RETURN NEW;
+END;
+$$ LANGUAGE PLPGSQL;
+
+CREATE TRIGGER no_overlapping_sups
+    BEFORE INSERT OR UPDATE ON config.db_patch_dependencies
+    FOR EACH ROW EXECUTE PROCEDURE evergreen.array_overlap_check ('supersedes');
+
+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 ('0526', :eg_version); -- miker
 
 CREATE TABLE config.bib_source (
        id              SERIAL  PRIMARY KEY,
@@ -794,4 +823,68 @@ BEGIN
 END;
 $$ LANGUAGE PLPGSQL;
 
+-- List applied db patches that are deprecated by (and block the application of) my_db_patch
+CREATE OR REPLACE FUNCTION evergreen.upgrade_list_applied_deprecates ( my_db_patch TEXT ) RETURNS SETOF TEXT AS $$
+    SELECT  DISTINCT l.version
+      FROM  config.upgrade_log l
+            JOIN config.db_patch_dependencies d ON (l.version::TEXT[] && d.deprecates)
+      WHERE d.db_patch = $1 
+$$ LANGUAGE SQL;
+
+-- List applied db patches that are superseded by (and block the application of) my_db_patch
+CREATE OR REPLACE FUNCTION evergreen.upgrade_list_applied_supersedes ( my_db_patch TEXT ) RETURNS SETOF TEXT AS $$
+    SELECT  DISTINCT l.version
+      FROM  config.upgrade_log l
+            JOIN config.db_patch_dependencies d ON (l.version::TEXT[] && d.supersedes)
+      WHERE d.db_patch = $1 
+$$ LANGUAGE SQL;
+
+-- List applied db patches that deprecates (and block the application of) my_db_patch
+CREATE OR REPLACE FUNCTION evergreen.upgrade_list_applied_deprecated ( my_db_patch TEXT ) RETURNS TEXT AS $$
+    SELECT  db_patch
+      FROM  config.db_patch_dependencies
+      WHERE ARRAY[$1]::TEXT[] && deprecates 
+$$ LANGUAGE SQL;
+
+-- List applied db patches that supersedes (and block the application of) my_db_patch
+CREATE OR REPLACE FUNCTION evergreen.upgrade_list_applied_superseded ( my_db_patch TEXT ) RETURNS TEXT AS $$
+    SELECT  db_patch
+      FROM  config.db_patch_dependencies
+      WHERE ARRAY[$1]::TEXT[] && supersedes 
+$$ LANGUAGE SQL;
+
+-- Make sure that no deprecated or superseded db patches are currently applied
+CREATE OR REPLACE FUNCTION evergreen.upgrade_verify_no_dep_conflicts ( my_db_patch TEXT ) RETURNS BOOL AS $$
+    SELECT  COUNT(*) = 0
+      FROM  (SELECT * FROM evergreen.upgrade_list_applied_deprecates( $1 )
+                UNION
+             SELECT * FROM evergreen.upgrade_list_applied_supersedes( $1 )
+                UNION
+             SELECT * FROM evergreen.upgrade_list_applied_deprecated( $1 )
+                UNION
+             SELECT * FROM evergreen.upgrade_list_applied_superseded( $1 ))x
+$$ LANGUAGE SQL;
+
+-- Raise an exception if there are, in fact, dep/sup confilct
+CREATE OR REPLACE FUNCTION evergreen.upgrade_deps_block_check ( my_db_patch TEXT, my_applied_to TEXT ) RETURNS BOOL AS $$
+BEGIN
+    IF NOT evergreen.upgrade_verify_no_dep_conflicts( my_db_patch ) THEN
+        RAISE EXCEPTION '
+Upgrade script % can not be applied:
+  applied deprecated scripts %
+  applied superseded scripts %
+  deprecated by %
+  superseded by %',
+            my_db_patch,
+            ARRAY_ACUM(evergreen.upgrade_list_applied_deprecates(my_db_patch)),
+            ARRAY_ACUM(evergreen.upgrade_list_applied_supersedes(my_db_patch)),
+            evergreen.upgrade_list_applied_deprecated(my_db_patch),
+            evergreen.upgrade_list_applied_superseded(my_db_patch);
+    END IF;
+
+    INSERT INTO config.upgrade_log (version, applied_to) VALUES (my_db_patch, my_applied_to);
+    RETURN TRUE;
+END;
+$$ LANGUAGE PLPGSQL;
+
 COMMIT;
index 6ed4588..2b3540c 100755 (executable)
@@ -96,9 +96,9 @@ cat sql_file_manifest | while read sql_file; do
   export PGHOST PGPORT PGDATABASE PGUSER PGPASSWORD
   # Hide most of the harmless messages that obscure real problems
   if [ -z "$VERBOSE" ]; then
-    psql -f $sql_file 2>&1 | grep -v NOTICE | grep -v "^INSERT"
+    psql -v eg_version=NULL -f $sql_file 2>&1 | grep -v NOTICE | grep -v "^INSERT"
   else
-    psql -f $sql_file
+    psql -v eg_version=NULL -f $sql_file
   fi
   if [ $? != 0 ]; then
     cat <<EOM
diff --git a/Open-ILS/src/sql/Pg/make-db-patch.pl b/Open-ILS/src/sql/Pg/make-db-patch.pl
new file mode 100755 (executable)
index 0000000..ffb1b01
--- /dev/null
@@ -0,0 +1,122 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2011 Equinox Software, Inc.
+# Galen Charlton <gmc@esilibrary.com>
+#
+# Make template for a new DB patch SQL file.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+# 
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+use strict;
+use warnings;
+
+use Getopt::Long;
+
+my $db_patch_num;
+my $patch_name;
+my @deprecates;
+my @supersedes;
+
+exit_usage() if $#ARGV == -1;
+GetOptions( 
+    'num=i' => \$db_patch_num,
+    'name=s' => \$patch_name,
+    'deprecates=i' => \@deprecates,
+    'supersedes=i' => \@supersedes,
+) or exit_usage();
+
+exit_usage('--num required') unless defined $db_patch_num;
+exit_usage('--name required') unless defined $patch_name;
+
+# pad to four digits
+$db_patch_num = sprintf('%-04.4d', $db_patch_num);
+$_ = sprintf('%-04.4d', $_) foreach @deprecates;
+$_ = sprintf('%-04.4d', $_) foreach @supersedes;
+
+# basic sanity checks
+my @existing = glob("upgrade/$db_patch_num.*");
+if (@existing) {    
+    print "Error: $db_patch_num is already used by $existing[0]\n";
+    exit(1);
+}
+foreach my $dep (@deprecates) {
+    if ($dep gt $db_patch_num) {
+        print "Error: deprecated patch $dep has a higher patch number than $db_patch_num\n";
+        exit(1);
+    }
+}
+foreach my $sup (@supersedes) {
+    if ($sup gt $db_patch_num) {
+        print "Error: superseded patch $sup has a higher patch number than $db_patch_num\n";
+        exit(1);
+    }
+}
+
+my $patch_file_name = "upgrade/$db_patch_num.$patch_name.sql";
+open OUT, '>', $patch_file_name or die "$): cannot open output file $patch_file_name: $!\n";
+
+print OUT <<_HEADER_;
+-- Evergreen DB patch $db_patch_num.$patch_name.sql
+--
+-- FIXME: insert description of change, if needed
+--
+BEGIN;
+
+_HEADER_
+
+if (@deprecates or @supersedes) {
+    my @ins_cols = ('db_patch');
+    my @ins_vals = ("'$db_patch_num'");
+    if (@deprecates) {
+        print OUT "-- Deprecates patch(es): " . join(', ', @deprecates) . "\n"; 
+        push @ins_cols, 'deprecates';
+        push @ins_vals, "ARRAY[" . join(', ', map { "'$_'" } @deprecates) . "]";
+    }
+    if (@supersedes) {
+        print OUT "-- Supersedes patch(es): " . join(', ', @supersedes) . "\n";
+        push @ins_cols, 'supersedes';
+        push @ins_vals, "ARRAY[" . join(', ', map { "'$_'" } @supersedes) . "]";
+    }
+    print OUT "INSERT INTO config.db_patch_dependencies (" .
+              join(', ', @ins_cols) .
+              ")\nVALUES (" .
+              join(', ', @ins_vals) .
+              ");\n";
+}
+
+print OUT <<_FOOTER_;
+
+-- check whether patch can be applied
+SELECT evergreen.update_deps_block_check('$db_patch_num', :eg_version);
+
+-- FIXME: add SQL statements to perform the upgrade
+
+COMMIT;
+_FOOTER_
+
+close OUT;
+print "Created new patch script $patch_file_name -- please go forth and edit.\n";
+
+sub exit_usage {
+    my $msg = shift;
+    print "$msg\n\n" if defined($msg);
+    print <<_HELP_;
+usage: $0 --num <patch_num> --name <patch_name> [--deprecates <num1>] [--supersedes <num2>]
+
+Make template for a DB patch SQL file.
+
+    --num          DB patch number
+    --name         descriptive part of patch filename 
+    --deprecates   patch(es) deprecated by this update
+    --supersedes   patch(es) superseded by this update
+_HELP_
+    exit 0;
+}
diff --git a/Open-ILS/src/sql/Pg/upgrade/0526.schema.upgrade-dep-tracking.sql b/Open-ILS/src/sql/Pg/upgrade/0526.schema.upgrade-dep-tracking.sql
new file mode 100644 (file)
index 0000000..e707053
--- /dev/null
@@ -0,0 +1,100 @@
+BEGIN;
+
+INSERT INTO config.upgrade_log (version) VALUES ('0526'); --miker
+
+CREATE TABLE config.db_patch_dependencies (
+  db_patch      TEXT PRIMARY KEY,
+  supersedes    TEXT[],
+  deprecates    TEXT[]
+);
+
+CREATE OR REPLACE FUNCTION evergreen.array_overlap_check (/* field */) RETURNS TRIGGER AS $$
+DECLARE
+    fld     TEXT;
+    cnt     INT;
+BEGIN
+    fld := TG_ARGV[1];
+    EXECUTE 'SELECT COUNT(*) FROM '|| TG_TABLE_SCHEMA ||'.'|| TG_TABLE_NAME ||' WHERE '|| fld ||' && ($1).'|| fld INTO cnt USING NEW;
+    IF cnt > 0 THEN
+        RAISE EXCEPTION 'Cannot insert duplicate array into field % of table %', fld, TG_TABLE_SCHEMA ||'.'|| TG_TABLE_NAME;
+    END IF;
+    RETURN NEW;
+END;
+$$ LANGUAGE PLPGSQL;
+
+CREATE TRIGGER no_overlapping_sups
+    BEFORE INSERT OR UPDATE ON config.db_patch_dependencies
+    FOR EACH ROW EXECUTE PROCEDURE evergreen.array_overlap_check ('supersedes');
+
+CREATE TRIGGER no_overlapping_deps
+    BEFORE INSERT OR UPDATE ON config.db_patch_dependencies
+    FOR EACH ROW EXECUTE PROCEDURE evergreen.array_overlap_check ('deprecates');
+
+ALTER TABLE config.upgrade_log
+    ADD COLUMN applied_to TEXT;
+
+-- List applied db patches that are deprecated by (and block the application of) my_db_patch
+CREATE OR REPLACE FUNCTION evergreen.upgrade_list_applied_deprecates ( my_db_patch TEXT ) RETURNS SETOF TEXT AS $$
+    SELECT  DISTINCT l.version
+      FROM  config.upgrade_log l
+            JOIN config.db_patch_dependencies d ON (l.version::TEXT[] && d.deprecates)
+      WHERE d.db_patch = $1
+$$ LANGUAGE SQL;
+
+-- List applied db patches that are superseded by (and block the application of) my_db_patch
+CREATE OR REPLACE FUNCTION evergreen.upgrade_list_applied_supersedes ( my_db_patch TEXT ) RETURNS SETOF TEXT AS $$
+    SELECT  DISTINCT l.version
+      FROM  config.upgrade_log l
+            JOIN config.db_patch_dependencies d ON (l.version::TEXT[] && d.supersedes)
+      WHERE d.db_patch = $1
+$$ LANGUAGE SQL;
+
+-- List applied db patches that deprecates (and block the application of) my_db_patch
+CREATE OR REPLACE FUNCTION evergreen.upgrade_list_applied_deprecated ( my_db_patch TEXT ) RETURNS TEXT AS $$
+    SELECT  db_patch
+      FROM  config.db_patch_dependencies
+      WHERE ARRAY[$1]::TEXT[] && deprecates
+$$ LANGUAGE SQL;
+
+-- List applied db patches that supersedes (and block the application of) my_db_patch
+CREATE OR REPLACE FUNCTION evergreen.upgrade_list_applied_superseded ( my_db_patch TEXT ) RETURNS TEXT AS $$
+    SELECT  db_patch
+      FROM  config.db_patch_dependencies
+      WHERE ARRAY[$1]::TEXT[] && supersedes
+$$ LANGUAGE SQL;
+
+-- Make sure that no deprecated or superseded db patches are currently applied
+CREATE OR REPLACE FUNCTION evergreen.upgrade_verify_no_dep_conflicts ( my_db_patch TEXT ) RETURNS BOOL AS $$
+    SELECT  COUNT(*) = 0
+      FROM  (SELECT * FROM evergreen.upgrade_list_applied_deprecates( $1 )
+                UNION
+             SELECT * FROM evergreen.upgrade_list_applied_supersedes( $1 )
+                UNION
+             SELECT * FROM evergreen.upgrade_list_applied_deprecated( $1 )
+                UNION
+             SELECT * FROM evergreen.upgrade_list_applied_superseded( $1 ))x
+$$ LANGUAGE SQL;
+
+-- Raise an exception if there are, in fact, dep/sup confilct
+CREATE OR REPLACE FUNCTION evergreen.upgrade_deps_block_check ( my_db_patch TEXT, my_applied_to TEXT ) RETURNS BOOL AS $$
+BEGIN
+    IF NOT evergreen.upgrade_verify_no_dep_conflicts( my_db_patch ) THEN
+        RAISE EXCEPTION '
+Upgrade script % can not be applied:
+  applied deprecated scripts %
+  applied superseded scripts %
+  deprecated by %
+  superseded by %',
+            my_db_patch,
+            ARRAY_ACUM(evergreen.upgrade_list_applied_deprecates(my_db_patch)),
+            ARRAY_ACUM(evergreen.upgrade_list_applied_supersedes(my_db_patch)),
+            evergreen.upgrade_list_applied_deprecated(my_db_patch),
+            evergreen.upgrade_list_applied_superseded(my_db_patch);
+    END IF;
+
+    INSERT INTO config.upgrade_log (version, applied_to) VALUES (my_db_patch, my_applied_to);
+    RETURN TRUE;
+END;
+$$ LANGUAGE PLPGSQL;
+
+COMMIT;
diff --git a/Open-ILS/src/support-scripts/authority_control_fields.pl b/Open-ILS/src/support-scripts/authority_control_fields.pl
deleted file mode 100755 (executable)
index d65e8a7..0000000
+++ /dev/null
@@ -1,564 +0,0 @@
-#!/usr/bin/perl
-# Copyright (C) 2010 Laurentian University
-# Author: Dan Scott <dscott@laurentian.ca>
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# of the License, or (at your option) any later version.
-
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-# ---------------------------------------------------------------
-
-use strict;
-use warnings;
-use DBI;
-use Getopt::Long;
-use MARC::Record;
-use MARC::File::XML (BinaryEncoding => 'UTF-8');
-use OpenSRF::System;
-use OpenILS::Utils::Fieldmapper;
-use OpenSRF::Utils::SettingsClient;
-use OpenSRF::EX qw/:try/;
-use Encode;
-use Unicode::Normalize;
-use OpenILS::Application::AppUtils;
-use Data::Dumper;
-use Pod::Usage qw/ pod2usage /;
-
-my ($start_id, $end_id);
-my $bootstrap = '/openils/conf/opensrf_core.xml';
-my @records;
-
-my %options;
-my $result = GetOptions(
-    \%options,
-    'configuration=s' => \$bootstrap,
-    'record=i' => \@records,
-    'all', 'help',
-    'start_id=i' => \$start_id,
-    'end_id=i' => \$end_id,
-);
-
-if (!$result or $options{help}) {
-    pod2usage(0);
-}
-
-OpenSRF::System->bootstrap_client(config_file => $bootstrap);
-Fieldmapper->import(IDL => OpenSRF::Utils::SettingsClient->new->config_value("IDL"));
-
-# must be loaded and initialized after the IDL is parsed
-use OpenILS::Utils::CStoreEditor;
-OpenILS::Utils::CStoreEditor::init();
-
-my $editor = OpenILS::Utils::CStoreEditor->new;
-my $undeleted;
-if ($options{all}) {
-    # get a list of all non-deleted records from Evergreen
-    # open-ils.cstore open-ils.cstore.direct.biblio.record_entry.id_list.atomic {"deleted":"f"}
-    $undeleted = $editor->request( 
-        'open-ils.cstore.direct.biblio.record_entry.id_list.atomic', 
-        [{deleted => 'f'}, {id => { '>' => 0}}]
-    );
-    @records = @$undeleted;
-}
-
-if ($start_id and $end_id) {
-    @records = ($start_id .. $end_id);
-}
-# print Dumper($undeleted, \@records);
-
-# Hash of controlled fields & subfields in bibliographic records, and their
-# corresponding controlling fields & subfields in the authority record
-#
-# So, if the bib 650$a can be controlled by an auth 150$a, that maps to:
-# 650 => { a => { 150 => 'a'}}
-my %controllees = (
-    100 => { a => { 100 => 'a' },
-             b => { 100 => 'b' },
-             c => { 100 => 'c' },
-             d => { 100 => 'd' },
-             e => { 100 => 'e' },
-             f => { 100 => 'f' },
-             g => { 100 => 'g' },
-             j => { 100 => 'j' },
-             k => { 100 => 'k' },
-             l => { 100 => 'l' },
-             n => { 100 => 'n' },
-             p => { 100 => 'p' },
-             q => { 100 => 'q' },
-             t => { 100 => 't' },
-             u => { 100 => 'u' },
-             4 => { 100 => '4' },
-    },
-    110 => { a => { 110 => 'a' },
-             b => { 110 => 'b' },
-             c => { 110 => 'c' },
-             d => { 110 => 'd' },
-             e => { 110 => 'e' },
-             f => { 110 => 'f' },
-             g => { 110 => 'g' },
-             k => { 110 => 'k' },
-             l => { 110 => 'l' },
-             n => { 110 => 'n' },
-             p => { 110 => 'p' },
-             t => { 110 => 't' },
-             u => { 110 => 'u' },
-             4 => { 110 => '4' },
-    },
-    111 => { a => { 111 => 'a' },
-             c => { 111 => 'c' },
-             d => { 111 => 'd' },
-             e => { 111 => 'e' },
-             f => { 111 => 'f' },
-             g => { 111 => 'g' },
-             j => { 111 => 'j' },
-             k => { 111 => 'k' },
-             l => { 111 => 'l' },
-             n => { 111 => 'n' },
-             p => { 111 => 'p' },
-             q => { 111 => 'q' },
-             t => { 111 => 't' },
-             u => { 111 => 'u' },
-             4 => { 111 => '4' },
-    },
-    130 => { a => { 130 => 'a' },
-             d => { 130 => 'd' },
-             f => { 130 => 'f' },
-             g => { 130 => 'g' },
-             h => { 130 => 'h' },
-             k => { 130 => 'k' },
-             l => { 130 => 'l' },
-             m => { 130 => 'm' },
-             n => { 130 => 'n' },
-             o => { 130 => 'o' },
-             p => { 130 => 'p' },
-             r => { 130 => 'r' },
-             s => { 130 => 's' },
-             t => { 130 => 't' },
-    },
-    600 => { a => { 100 => 'a' },
-             b => { 100 => 'b' },
-             c => { 100 => 'c' },
-             d => { 100 => 'd' },
-             e => { 100 => 'e' },
-             f => { 100 => 'f' },
-             g => { 100 => 'g' },
-             h => { 100 => 'h' },
-             j => { 100 => 'j' },
-             k => { 100 => 'k' },
-             l => { 100 => 'l' },
-             m => { 100 => 'm' },
-             n => { 100 => 'n' },
-             o => { 100 => 'o' },
-             p => { 100 => 'p' },
-             q => { 100 => 'q' },
-             r => { 100 => 'r' },
-             s => { 100 => 's' },
-             t => { 100 => 't' },
-             v => { 100 => 'v' },
-             x => { 100 => 'x' },
-             y => { 100 => 'y' },
-             z => { 100 => 'z' },
-             4 => { 100 => '4' },
-    },
-    610 => { a => { 110 => 'a' },
-             b => { 110 => 'b' },
-             c => { 110 => 'c' },
-             d => { 110 => 'd' },
-             e => { 110 => 'e' },
-             f => { 110 => 'f' },
-             g => { 110 => 'g' },
-             h => { 110 => 'h' },
-             k => { 110 => 'k' },
-             l => { 110 => 'l' },
-             m => { 110 => 'm' },
-             n => { 110 => 'n' },
-             o => { 110 => 'o' },
-             p => { 110 => 'p' },
-             r => { 110 => 'r' },
-             s => { 110 => 's' },
-             t => { 110 => 't' },
-             v => { 110 => 'v' },
-             x => { 110 => 'x' },
-             y => { 110 => 'y' },
-             z => { 110 => 'z' },
-    },
-    611 => { a => { 111 => 'a' },
-             c => { 111 => 'c' },
-             d => { 111 => 'd' },
-             e => { 111 => 'e' },
-             f => { 111 => 'f' },
-             g => { 111 => 'g' },
-             h => { 111 => 'h' },
-             j => { 111 => 'j' },
-             k => { 111 => 'k' },
-             l => { 111 => 'l' },
-             n => { 111 => 'n' },
-             p => { 111 => 'p' },
-             q => { 111 => 'q' },
-             s => { 111 => 's' },
-             t => { 111 => 't' },
-             v => { 111 => 'v' },
-             x => { 111 => 'x' },
-             y => { 111 => 'y' },
-             z => { 111 => 'z' },
-    },
-    630 => { a => { 130 => 'a' },
-             d => { 130 => 'd' },
-             f => { 130 => 'f' },
-             g => { 130 => 'g' },
-             h => { 130 => 'h' },
-             k => { 130 => 'k' },
-             l => { 130 => 'l' },
-             m => { 130 => 'm' },
-             n => { 130 => 'n' },
-             o => { 130 => 'o' },
-             p => { 130 => 'p' },
-             r => { 130 => 'r' },
-             s => { 130 => 's' },
-             t => { 130 => 't' },
-             v => { 130 => 'v' },
-             x => { 130 => 'x' },
-             y => { 130 => 'y' },
-             z => { 130 => 'z' },
-    },
-    648 => { a => { 148 => 'a' },
-             v => { 148 => 'v' },
-             x => { 148 => 'x' },
-             y => { 148 => 'y' },
-             z => { 148 => 'z' },
-    },
-    650 => { a => { 150 => 'a' },
-             b => { 150 => 'b' },
-             v => { 150 => 'v' },
-             x => { 150 => 'x' },
-             y => { 150 => 'y' },
-             z => { 150 => 'z' },
-    },
-    651 => { a => { 151 => 'a' },
-             v => { 151 => 'v' },
-             x => { 151 => 'x' },
-             y => { 151 => 'y' },
-             z => { 151 => 'z' },
-    },
-    655 => { a => { 155 => 'a' },
-             v => { 155 => 'v' },
-             x => { 155 => 'x' },
-             y => { 155 => 'y' },
-             z => { 155 => 'z' },
-    },
-    700 => { a => { 100 => 'a' },
-             b => { 100 => 'b' },
-             c => { 100 => 'c' },
-             d => { 100 => 'd' },
-             e => { 100 => 'e' },
-             f => { 100 => 'f' },
-             g => { 100 => 'g' },
-             j => { 100 => 'j' },
-             k => { 100 => 'k' },
-             l => { 100 => 'l' },
-             n => { 100 => 'n' },
-             p => { 100 => 'p' },
-             q => { 100 => 'q' },
-             t => { 100 => 't' },
-             u => { 100 => 'u' },
-             4 => { 100 => '4' },
-    },
-    710 => { a => { 110 => 'a' },
-             b => { 110 => 'b' },
-             c => { 110 => 'c' },
-             d => { 110 => 'd' },
-             e => { 110 => 'e' },
-             f => { 110 => 'f' },
-             g => { 110 => 'g' },
-             k => { 110 => 'k' },
-             l => { 110 => 'l' },
-             n => { 110 => 'n' },
-             p => { 110 => 'p' },
-             t => { 110 => 't' },
-             u => { 110 => 'u' },
-             4 => { 110 => '4' },
-    },
-    711 => { a => { 111 => 'a' },
-             c => { 111 => 'c' },
-             d => { 111 => 'd' },
-             e => { 111 => 'e' },
-             f => { 111 => 'f' },
-             g => { 111 => 'g' },
-             j => { 111 => 'j' },
-             k => { 111 => 'k' },
-             l => { 111 => 'l' },
-             n => { 111 => 'n' },
-             p => { 111 => 'p' },
-             q => { 111 => 'q' },
-             t => { 111 => 't' },
-             u => { 111 => 'u' },
-             4 => { 111 => '4' },
-    },
-    730 => { a => { 130 => 'a' },
-             d => { 130 => 'd' },
-             f => { 130 => 'f' },
-             g => { 130 => 'g' },
-             h => { 130 => 'h' },
-             k => { 130 => 'k' },
-             l => { 130 => 'l' },
-             m => { 130 => 'm' },
-             n => { 130 => 'n' },
-             o => { 130 => 'o' },
-             p => { 130 => 'p' },
-             r => { 130 => 'r' },
-             s => { 130 => 's' },
-             t => { 130 => 't' },
-    },
-    751 => { a => { 151 => 'a' },
-             v => { 151 => 'v' },
-             x => { 151 => 'x' },
-             y => { 151 => 'y' },
-             z => { 151 => 'z' },
-    },
-    830 => { a => { 830 => 'a' },
-             d => { 130 => 'd' },
-             f => { 130 => 'f' },
-             g => { 130 => 'g' },
-             h => { 130 => 'h' },
-             k => { 130 => 'k' },
-             l => { 130 => 'l' },
-             m => { 130 => 'm' },
-             n => { 130 => 'n' },
-             o => { 130 => 'o' },
-             p => { 130 => 'p' },
-             r => { 130 => 'r' },
-             s => { 130 => 's' },
-             t => { 130 => 't' },
-    },
-);
-
-foreach my $rec_id (@records) {
-    # print "$rec_id\n";
-
-    my $e = OpenILS::Utils::CStoreEditor->new();
-    # State variable; was the record changed?
-    my $changed;
-
-    # get the record
-    my $record = $e->retrieve_biblio_record_entry($rec_id);
-    next unless $record;
-    # print Dumper($record);
-
-    try {
-        my $marc = MARC::Record->new_from_xml($record->marc());
-
-        # get the list of controlled fields
-        my @c_fields = keys %controllees;
-
-        foreach my $c_tag (@c_fields) {
-            my @c_subfields = keys %{$controllees{"$c_tag"}};
-            # print "Field: $field subfields: ";
-            # foreach (@subfields) { print "$_ "; }
-
-            # Get the MARCXML from the record and check for controlled fields/subfields
-            my @bib_fields = ($marc->field($c_tag));
-            foreach my $bib_field (@bib_fields) {
-                # print $_->as_formatted(); 
-                my %match_subfields;
-                my $match_tag;
-                my @searches;
-                foreach my $c_subfield (@c_subfields) {
-                    my $sf = $bib_field->subfield($c_subfield);
-                    if ($sf) {
-                        # Give me the first element of the list of authority controlling tags for this subfield
-                        # XXX Will we need to support more than one controlling tag per subfield? Probably. That
-                        # will suck. Oh well, leave that up to Ole to implement.
-                        $match_subfields{$c_subfield} = (keys %{$controllees{$c_tag}{$c_subfield}})[0];
-                        $match_tag = $match_subfields{$c_subfield};
-                        push @searches, {term => $sf, subfield => $c_subfield};
-                    }
-                }
-                # print Dumper(\%match_subfields);
-                next if !$match_tag;
-
-                my @tags = ($match_tag);
-
-                # print "Controlling tag: $c_tag and match tag $match_tag\n";
-                # print Dumper(\@tags, \@searches);
-
-                # Now we've built up a complete set of matching controlled
-                # subfields for this particular field; let's check to see if
-                # we have a matching authority record
-                my $session = OpenSRF::AppSession->create("open-ils.search");
-                my $validates = $session->request("open-ils.search.authority.validate.tag.id_list", 
-                    "tags", \@tags, "searches", \@searches
-                )->gather();
-                $session->disconnect();
-
-                # print Dumper($validates);
-
-                # Protect against failed (error condition) search request
-                if (!$validates) {
-                    print STDERR "Search for matching authority failed; record # $rec_id\n";
-                    next;
-                }
-
-                if (scalar(@$validates) == 0) {
-                    next;
-                }
-
-                # Iterate through the returned authority record IDs to delete any
-                # matching $0 subfields already in the bib record
-                foreach my $auth_zero (@$validates) {
-                    $bib_field->delete_subfield(code => '0', match => qr/\)$auth_zero$/);
-                }
-
-                # Okay, we have a matching authority control; time to
-                # add the magical subfield 0. Use the first returned auth
-                # record as a match.
-                my $auth_id = @$validates[0];
-                my $auth_rec = $e->retrieve_authority_record_entry($auth_id);
-                my $auth_marc = MARC::Record->new_from_xml($auth_rec->marc());
-                my $cni = $auth_marc->field('003')->data();
-                
-                $bib_field->add_subfields('0' => "($cni)$auth_id");
-                $changed = 1;
-            }
-        }
-        if ($changed) {
-            my $editor = OpenILS::Utils::CStoreEditor->new(xact=>1);
-            # print $marc->as_formatted();
-            my $xml = $marc->as_xml_record();
-            $xml =~ s/\n//sgo;
-            $xml =~ s/^<\?xml.+\?\s*>//go;
-            $xml =~ s/>\s+</></go;
-            $xml =~ s/\p{Cc}//go;
-            $xml = OpenILS::Application::AppUtils->entityize($xml);
-
-            $record->marc($xml);
-            $editor->update_biblio_record_entry($record);
-            $editor->commit();
-        }
-    } otherwise {
-        my $err = shift;
-        print STDERR "\nRecord # $rec_id : $err\n";
-        import MARC::File::XML; # reset SAX parser so that one bad record doesn't kill the entire process
-    }
-}
-
-__END__
-
-=head1 NAME
-
-authority_control_fields.pl - Controls fields in bibliographic records with authorities in Evergreen
-
-=head1 SYNOPSIS
-
-C<authority_control_fields.pl> [B<--configuration>=I<opensrf_core.conf>]
-[[B<--record>=I<record>[ B<--record>=I<record>]]] | [B<--all>] | [B<--start_id>=I<start-ID> B<--end_id>=I<end-ID>]
-
-=head1 DESCRIPTION
-
-For a given set of records:
-
-=over
-
-=item * Iterate through the list of fields that are controlled fields
-
-=item * Iterate through the list of subfields that are controlled for
-that given field
-
-=item * Search for a matching authority record for that combination of
-field + subfield(s)
-
-=over
-
-=item * If we find a match, then add a $0 subfield to that field identifying
-the controlling authority record
-
-=item * If we do not find a match, then insert a row into an "uncontrolled"
-table identifying the record ID, field, and subfield(s) that were not controlled
-
-=back
-
-=item * Iterate through the list of floating subdivisions
-
-=over
-
-=item * If we find a match, then add a $0 subfield to that field identifying
-the controlling authority record
-
-=item * If we do not find a match, then insert a row into an "uncontrolled"
-table identifying the record ID, field, and subfield(s) that were not controlled
-
-=back
-
-=item * If we changed the record, update it in the database
-
-=back
-
-=head1 OPTIONS
-
-=over
-
-=item * B<-c> I<config-file>, B<--configuration>=I<config-file>
-
-Specifies the OpenSRF configuration file used to connect to the OpenSRF router.
-Defaults to F</openils/conf/opensrf_core.xml>
-
-=item * B<-r> I<record-ID>, B<--record>=I<record-ID>
-
-Specifies the bibliographic record ID (found in the C<biblio.record_entry.id>
-column) of the record to process. This option may be specified more than once
-to process multiple records in a single run.
-
-=item * B<-a>, B<--all>
-
-Specifies that all bibliographic records should be processed. For large
-databases, this may take an extraordinarily long amount of time.
-
-=item * B<-s> I<start-ID>, B<--start_id>=I<start-ID>
-
-Specifies the starting ID of the range of bibliographic records to process.
-This option is ignored unless it is accompanied by the B<-e> or B<--end_id>
-option.
-
-=item * B<-e> I<end-ID>, B<--end_id>=I<end-ID>
-
-Specifies the ending ID of the range of bibliographic records to process.
-This option is ignored unless it is accompanied by the B<-s> or B<--start>
-option.
-
-=back
-
-=head1 EXAMPLES
-
-    authority_control_fields.pl --start_id 1 --end_id 50000
-
-Processes the bibliographic records with IDs between 1 and 50,000 using the
-default OpenSRF configuration file for connection information.
-
-=head1 AUTHOR
-
-Dan Scott <dscott@laurentian.ca>
-
-=head1 COPYRIGHT AND LICENSE
-
-Copyright 2010-2011 by Dan Scott
-
-This program is free software; you can redistribute it and/or
-modify it under the terms of the GNU General Public License
-as published by the Free Software Foundation; either version 2
-of the License, or (at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program; if not, write to the Free Software
-Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
-
-=cut
-
diff --git a/Open-ILS/src/support-scripts/authority_control_fields.pl.in b/Open-ILS/src/support-scripts/authority_control_fields.pl.in
new file mode 100755 (executable)
index 0000000..07c9f77
--- /dev/null
@@ -0,0 +1,563 @@
+#!/usr/bin/perl
+# Copyright (C) 2010-2011 Laurentian University
+# Author: Dan Scott <dscott@laurentian.ca>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# ---------------------------------------------------------------
+
+use strict;
+use warnings;
+use DBI;
+use Getopt::Long;
+use MARC::Record;
+use MARC::File::XML (BinaryEncoding => 'UTF-8');
+use OpenSRF::System;
+use OpenILS::Utils::Fieldmapper;
+use OpenSRF::Utils::SettingsClient;
+use OpenSRF::EX qw/:try/;
+use Encode;
+use Unicode::Normalize;
+use OpenILS::Application::AppUtils;
+use Data::Dumper;
+use Pod::Usage qw/ pod2usage /;
+
+my ($start_id, $end_id);
+my $bootstrap = '@sysconfdir@/opensrf_core.xml';
+my @records;
+
+my %options;
+my $result = GetOptions(
+    \%options,
+    'configuration=s' => \$bootstrap,
+    'record=i' => \@records,
+    'all', 'help',
+    'start_id=i' => \$start_id,
+    'end_id=i' => \$end_id,
+);
+
+if (!$result or $options{help}) {
+    pod2usage(0);
+}
+
+OpenSRF::System->bootstrap_client(config_file => $bootstrap);
+Fieldmapper->import(IDL => OpenSRF::Utils::SettingsClient->new->config_value("IDL"));
+
+# must be loaded and initialized after the IDL is parsed
+use OpenILS::Utils::CStoreEditor;
+OpenILS::Utils::CStoreEditor::init();
+
+my $e = OpenILS::Utils::CStoreEditor->new;
+my $undeleted;
+if ($options{all}) {
+    # get a list of all non-deleted records from Evergreen
+    # open-ils.cstore open-ils.cstore.direct.biblio.record_entry.id_list.atomic {"deleted":"f"}
+    $undeleted = $e->request( 
+        'open-ils.cstore.direct.biblio.record_entry.id_list.atomic', 
+        [{deleted => 'f'}, {id => { '>' => 0}}]
+    );
+    @records = @$undeleted;
+}
+
+if ($start_id and $end_id) {
+    @records = ($start_id .. $end_id);
+}
+# print Dumper($undeleted, \@records);
+
+# Hash of controlled fields & subfields in bibliographic records, and their
+# corresponding controlling fields & subfields in the authority record
+#
+# So, if the bib 650$a can be controlled by an auth 150$a, that maps to:
+# 650 => { a => { 150 => 'a'}}
+my %controllees = (
+    100 => { a => { 100 => 'a' },
+             b => { 100 => 'b' },
+             c => { 100 => 'c' },
+             d => { 100 => 'd' },
+             e => { 100 => 'e' },
+             f => { 100 => 'f' },
+             g => { 100 => 'g' },
+             j => { 100 => 'j' },
+             k => { 100 => 'k' },
+             l => { 100 => 'l' },
+             n => { 100 => 'n' },
+             p => { 100 => 'p' },
+             q => { 100 => 'q' },
+             t => { 100 => 't' },
+             u => { 100 => 'u' },
+             4 => { 100 => '4' },
+    },
+    110 => { a => { 110 => 'a' },
+             b => { 110 => 'b' },
+             c => { 110 => 'c' },
+             d => { 110 => 'd' },
+             e => { 110 => 'e' },
+             f => { 110 => 'f' },
+             g => { 110 => 'g' },
+             k => { 110 => 'k' },
+             l => { 110 => 'l' },
+             n => { 110 => 'n' },
+             p => { 110 => 'p' },
+             t => { 110 => 't' },
+             u => { 110 => 'u' },
+             4 => { 110 => '4' },
+    },
+    111 => { a => { 111 => 'a' },
+             c => { 111 => 'c' },
+             d => { 111 => 'd' },
+             e => { 111 => 'e' },
+             f => { 111 => 'f' },
+             g => { 111 => 'g' },
+             j => { 111 => 'j' },
+             k => { 111 => 'k' },
+             l => { 111 => 'l' },
+             n => { 111 => 'n' },
+             p => { 111 => 'p' },
+             q => { 111 => 'q' },
+             t => { 111 => 't' },
+             u => { 111 => 'u' },
+             4 => { 111 => '4' },
+    },
+    130 => { a => { 130 => 'a' },
+             d => { 130 => 'd' },
+             f => { 130 => 'f' },
+             g => { 130 => 'g' },
+             h => { 130 => 'h' },
+             k => { 130 => 'k' },
+             l => { 130 => 'l' },
+             m => { 130 => 'm' },
+             n => { 130 => 'n' },
+             o => { 130 => 'o' },
+             p => { 130 => 'p' },
+             r => { 130 => 'r' },
+             s => { 130 => 's' },
+             t => { 130 => 't' },
+    },
+    600 => { a => { 100 => 'a' },
+             b => { 100 => 'b' },
+             c => { 100 => 'c' },
+             d => { 100 => 'd' },
+             e => { 100 => 'e' },
+             f => { 100 => 'f' },
+             g => { 100 => 'g' },
+             h => { 100 => 'h' },
+             j => { 100 => 'j' },
+             k => { 100 => 'k' },
+             l => { 100 => 'l' },
+             m => { 100 => 'm' },
+             n => { 100 => 'n' },
+             o => { 100 => 'o' },
+             p => { 100 => 'p' },
+             q => { 100 => 'q' },
+             r => { 100 => 'r' },
+             s => { 100 => 's' },
+             t => { 100 => 't' },
+             v => { 100 => 'v' },
+             x => { 100 => 'x' },
+             y => { 100 => 'y' },
+             z => { 100 => 'z' },
+             4 => { 100 => '4' },
+    },
+    610 => { a => { 110 => 'a' },
+             b => { 110 => 'b' },
+             c => { 110 => 'c' },
+             d => { 110 => 'd' },
+             e => { 110 => 'e' },
+             f => { 110 => 'f' },
+             g => { 110 => 'g' },
+             h => { 110 => 'h' },
+             k => { 110 => 'k' },
+             l => { 110 => 'l' },
+             m => { 110 => 'm' },
+             n => { 110 => 'n' },
+             o => { 110 => 'o' },
+             p => { 110 => 'p' },
+             r => { 110 => 'r' },
+             s => { 110 => 's' },
+             t => { 110 => 't' },
+             v => { 110 => 'v' },
+             x => { 110 => 'x' },
+             y => { 110 => 'y' },
+             z => { 110 => 'z' },
+    },
+    611 => { a => { 111 => 'a' },
+             c => { 111 => 'c' },
+             d => { 111 => 'd' },
+             e => { 111 => 'e' },
+             f => { 111 => 'f' },
+             g => { 111 => 'g' },
+             h => { 111 => 'h' },
+             j => { 111 => 'j' },
+             k => { 111 => 'k' },
+             l => { 111 => 'l' },
+             n => { 111 => 'n' },
+             p => { 111 => 'p' },
+             q => { 111 => 'q' },
+             s => { 111 => 's' },
+             t => { 111 => 't' },
+             v => { 111 => 'v' },
+             x => { 111 => 'x' },
+             y => { 111 => 'y' },
+             z => { 111 => 'z' },
+    },
+    630 => { a => { 130 => 'a' },
+             d => { 130 => 'd' },
+             f => { 130 => 'f' },
+             g => { 130 => 'g' },
+             h => { 130 => 'h' },
+             k => { 130 => 'k' },
+             l => { 130 => 'l' },
+             m => { 130 => 'm' },
+             n => { 130 => 'n' },
+             o => { 130 => 'o' },
+             p => { 130 => 'p' },
+             r => { 130 => 'r' },
+             s => { 130 => 's' },
+             t => { 130 => 't' },
+             v => { 130 => 'v' },
+             x => { 130 => 'x' },
+             y => { 130 => 'y' },
+             z => { 130 => 'z' },
+    },
+    648 => { a => { 148 => 'a' },
+             v => { 148 => 'v' },
+             x => { 148 => 'x' },
+             y => { 148 => 'y' },
+             z => { 148 => 'z' },
+    },
+    650 => { a => { 150 => 'a' },
+             b => { 150 => 'b' },
+             v => { 150 => 'v' },
+             x => { 150 => 'x' },
+             y => { 150 => 'y' },
+             z => { 150 => 'z' },
+    },
+    651 => { a => { 151 => 'a' },
+             v => { 151 => 'v' },
+             x => { 151 => 'x' },
+             y => { 151 => 'y' },
+             z => { 151 => 'z' },
+    },
+    655 => { a => { 155 => 'a' },
+             v => { 155 => 'v' },
+             x => { 155 => 'x' },
+             y => { 155 => 'y' },
+             z => { 155 => 'z' },
+    },
+    700 => { a => { 100 => 'a' },
+             b => { 100 => 'b' },
+             c => { 100 => 'c' },
+             d => { 100 => 'd' },
+             e => { 100 => 'e' },
+             f => { 100 => 'f' },
+             g => { 100 => 'g' },
+             j => { 100 => 'j' },
+             k => { 100 => 'k' },
+             l => { 100 => 'l' },
+             n => { 100 => 'n' },
+             p => { 100 => 'p' },
+             q => { 100 => 'q' },
+             t => { 100 => 't' },
+             u => { 100 => 'u' },
+             4 => { 100 => '4' },
+    },
+    710 => { a => { 110 => 'a' },
+             b => { 110 => 'b' },
+             c => { 110 => 'c' },
+             d => { 110 => 'd' },
+             e => { 110 => 'e' },
+             f => { 110 => 'f' },
+             g => { 110 => 'g' },
+             k => { 110 => 'k' },
+             l => { 110 => 'l' },
+             n => { 110 => 'n' },
+             p => { 110 => 'p' },
+             t => { 110 => 't' },
+             u => { 110 => 'u' },
+             4 => { 110 => '4' },
+    },
+    711 => { a => { 111 => 'a' },
+             c => { 111 => 'c' },
+             d => { 111 => 'd' },
+             e => { 111 => 'e' },
+             f => { 111 => 'f' },
+             g => { 111 => 'g' },
+             j => { 111 => 'j' },
+             k => { 111 => 'k' },
+             l => { 111 => 'l' },
+             n => { 111 => 'n' },
+             p => { 111 => 'p' },
+             q => { 111 => 'q' },
+             t => { 111 => 't' },
+             u => { 111 => 'u' },
+             4 => { 111 => '4' },
+    },
+    730 => { a => { 130 => 'a' },
+             d => { 130 => 'd' },
+             f => { 130 => 'f' },
+             g => { 130 => 'g' },
+             h => { 130 => 'h' },
+             k => { 130 => 'k' },
+             l => { 130 => 'l' },
+             m => { 130 => 'm' },
+             n => { 130 => 'n' },
+             o => { 130 => 'o' },
+             p => { 130 => 'p' },
+             r => { 130 => 'r' },
+             s => { 130 => 's' },
+             t => { 130 => 't' },
+    },
+    751 => { a => { 151 => 'a' },
+             v => { 151 => 'v' },
+             x => { 151 => 'x' },
+             y => { 151 => 'y' },
+             z => { 151 => 'z' },
+    },
+    830 => { a => { 830 => 'a' },
+             d => { 130 => 'd' },
+             f => { 130 => 'f' },
+             g => { 130 => 'g' },
+             h => { 130 => 'h' },
+             k => { 130 => 'k' },
+             l => { 130 => 'l' },
+             m => { 130 => 'm' },
+             n => { 130 => 'n' },
+             o => { 130 => 'o' },
+             p => { 130 => 'p' },
+             r => { 130 => 'r' },
+             s => { 130 => 's' },
+             t => { 130 => 't' },
+    },
+);
+
+foreach my $rec_id (@records) {
+    # print "$rec_id\n";
+
+    # State variable; was the record changed?
+    my $changed;
+
+    # get the record
+    my $record = $e->retrieve_biblio_record_entry($rec_id);
+    next unless $record;
+    # print Dumper($record);
+
+    try {
+        my $marc = MARC::Record->new_from_xml($record->marc());
+
+        # get the list of controlled fields
+        my @c_fields = keys %controllees;
+
+        foreach my $c_tag (@c_fields) {
+            my @c_subfields = keys %{$controllees{"$c_tag"}};
+            # print "Field: $field subfields: ";
+            # foreach (@subfields) { print "$_ "; }
+
+            # Get the MARCXML from the record and check for controlled fields/subfields
+            my @bib_fields = ($marc->field($c_tag));
+            foreach my $bib_field (@bib_fields) {
+                # print $_->as_formatted(); 
+                my %match_subfields;
+                my $match_tag;
+                my @searches;
+                foreach my $c_subfield (@c_subfields) {
+                    my $sf = $bib_field->subfield($c_subfield);
+                    if ($sf) {
+                        # Give me the first element of the list of authority controlling tags for this subfield
+                        # XXX Will we need to support more than one controlling tag per subfield? Probably. That
+                        # will suck. Oh well, leave that up to Ole to implement.
+                        $match_subfields{$c_subfield} = (keys %{$controllees{$c_tag}{$c_subfield}})[0];
+                        $match_tag = $match_subfields{$c_subfield};
+                        push @searches, {term => $sf, subfield => $c_subfield};
+                    }
+                }
+                # print Dumper(\%match_subfields);
+                next if !$match_tag;
+
+                my @tags = ($match_tag);
+
+                # print "Controlling tag: $c_tag and match tag $match_tag\n";
+                # print Dumper(\@tags, \@searches);
+
+                # Now we've built up a complete set of matching controlled
+                # subfields for this particular field; let's check to see if
+                # we have a matching authority record
+                my $session = OpenSRF::AppSession->create("open-ils.search");
+                my $validates = $session->request("open-ils.search.authority.validate.tag.id_list", 
+                    "tags", \@tags, "searches", \@searches
+                )->gather();
+                $session->disconnect();
+
+                # print Dumper($validates);
+
+                # Protect against failed (error condition) search request
+                if (!$validates) {
+                    print STDERR "Search for matching authority failed; record # $rec_id\n";
+                    next;
+                }
+
+                if (scalar(@$validates) == 0) {
+                    next;
+                }
+
+                # Iterate through the returned authority record IDs to delete any
+                # matching $0 subfields already in the bib record
+                foreach my $auth_zero (@$validates) {
+                    $bib_field->delete_subfield(code => '0', match => qr/\)$auth_zero$/);
+                }
+
+                # Okay, we have a matching authority control; time to
+                # add the magical subfield 0. Use the first returned auth
+                # record as a match.
+                my $auth_id = @$validates[0];
+                my $auth_rec = $e->retrieve_authority_record_entry($auth_id);
+                my $auth_marc = MARC::Record->new_from_xml($auth_rec->marc());
+                my $cni = $auth_marc->field('003')->data();
+                
+                $bib_field->add_subfields('0' => "($cni)$auth_id");
+                $changed = 1;
+            }
+        }
+        if ($changed) {
+            my $editor = OpenILS::Utils::CStoreEditor->new(xact=>1);
+            # print $marc->as_formatted();
+            my $xml = $marc->as_xml_record();
+            $xml =~ s/\n//sgo;
+            $xml =~ s/^<\?xml.+\?\s*>//go;
+            $xml =~ s/>\s+</></go;
+            $xml =~ s/\p{Cc}//go;
+            $xml = OpenILS::Application::AppUtils->entityize($xml);
+
+            $record->marc($xml);
+            $editor->update_biblio_record_entry($record);
+            $editor->commit();
+        }
+    } otherwise {
+        my $err = shift;
+        print STDERR "\nRecord # $rec_id : $err\n";
+        import MARC::File::XML; # reset SAX parser so that one bad record doesn't kill the entire process
+    }
+}
+
+__END__
+
+=head1 NAME
+
+authority_control_fields.pl - Controls fields in bibliographic records with authorities in Evergreen
+
+=head1 SYNOPSIS
+
+C<authority_control_fields.pl> [B<--configuration>=I<opensrf_core.conf>]
+[[B<--record>=I<record>[ B<--record>=I<record>]]] | [B<--all>] | [B<--start_id>=I<start-ID> B<--end_id>=I<end-ID>]
+
+=head1 DESCRIPTION
+
+For a given set of records:
+
+=over
+
+=item * Iterate through the list of fields that are controlled fields
+
+=item * Iterate through the list of subfields that are controlled for
+that given field
+
+=item * Search for a matching authority record for that combination of
+field + subfield(s)
+
+=over
+
+=item * If we find a match, then add a $0 subfield to that field identifying
+the controlling authority record
+
+=item * If we do not find a match, then insert a row into an "uncontrolled"
+table identifying the record ID, field, and subfield(s) that were not controlled
+
+=back
+
+=item * Iterate through the list of floating subdivisions
+
+=over
+
+=item * If we find a match, then add a $0 subfield to that field identifying
+the controlling authority record
+
+=item * If we do not find a match, then insert a row into an "uncontrolled"
+table identifying the record ID, field, and subfield(s) that were not controlled
+
+=back
+
+=item * If we changed the record, update it in the database
+
+=back
+
+=head1 OPTIONS
+
+=over
+
+=item * B<-c> I<config-file>, B<--configuration>=I<config-file>
+
+Specifies the OpenSRF configuration file used to connect to the OpenSRF router.
+Defaults to F<@sysconfdir@/opensrf_core.xml>
+
+=item * B<-r> I<record-ID>, B<--record>=I<record-ID>
+
+Specifies the bibliographic record ID (found in the C<biblio.record_entry.id>
+column) of the record to process. This option may be specified more than once
+to process multiple records in a single run.
+
+=item * B<-a>, B<--all>
+
+Specifies that all bibliographic records should be processed. For large
+databases, this may take an extraordinarily long amount of time.
+
+=item * B<-s> I<start-ID>, B<--start_id>=I<start-ID>
+
+Specifies the starting ID of the range of bibliographic records to process.
+This option is ignored unless it is accompanied by the B<-e> or B<--end_id>
+option.
+
+=item * B<-e> I<end-ID>, B<--end_id>=I<end-ID>
+
+Specifies the ending ID of the range of bibliographic records to process.
+This option is ignored unless it is accompanied by the B<-s> or B<--start>
+option.
+
+=back
+
+=head1 EXAMPLES
+
+    authority_control_fields.pl --start_id 1 --end_id 50000
+
+Processes the bibliographic records with IDs between 1 and 50,000 using the
+default OpenSRF configuration file for connection information.
+
+=head1 AUTHOR
+
+Dan Scott <dscott@laurentian.ca>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2010-2011 by Dan Scott
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+=cut
+
index 69526af..4e53914 100755 (executable)
@@ -48,9 +48,11 @@ my %defaults = (
     'user=s'        => 'admin',
     'password=s'    => '',
     'tempdir=s'     => '',
+    'spoolfile=s'   => '',
     'nolockfile'    => 1,
     'queue=i'       => 1,
     'noqueue'       => 0,
+    'nodaemon'      => 0,
     'wait=i'        => 5,
     'import-by-queue' => 0
 );
@@ -154,10 +156,16 @@ sub xml_import {
 
 sub old_process_batch_data {
     my $data = shift or $logger->error("process_batch_data called without any data");
+    my $isfile = shift;
     $data or return;
 
     my $handle;
-    open $handle, '<', \$data; 
+    if ($isfile) {
+        $handle = $data;
+    } else {
+        open $handle, '<', \$data; 
+    }
+
     my $batch = MARC::Batch->new('USMARC', $handle);
     $batch->strict_off;
 
@@ -315,13 +323,19 @@ sub bib_queue_import {
 
 sub process_batch_data {
     my $data = shift or $logger->error("process_batch_data called without any data");
+    my $isfile = shift;
     $data or return;
 
     $vl_ses = OpenSRF::AppSession->create('open-ils.vandelay');
 
-    my ($handle, $tempfile) = File::Temp->tempfile("$0_XXXX", DIR => $tempdir) or die "Cannot write tempfile in $tempdir";
-    print $handle $data;
-    close $handle;
+    my ($handle, $tempfile);
+    if (!$isfile) {
+        ($handle, $tempfile) = File::Temp->tempfile("$0_XXXX", DIR => $tempdir) or die "Cannot write tempfile in $tempdir";
+        print $handle $data;
+        close $handle;
+    } else {
+        $tempfile = $data;
+    }
        
     $logger->info("Calling process_spool on tempfile $tempfile (queue: $queue_id; source: $bib_source)");
     my $rec_ids = process_spool($tempfile);
@@ -358,12 +372,15 @@ sub process_request {   # The core Net::Server method
         exit;
     }
 
-    my $data;
+    my $data = '';
     eval {
         local $SIG{ALRM} = sub { die "alarm\n" };
         alarm $wait_time; # prevent accidental tie ups of backend processes
         local $/ = "\x1D"; # MARC record separator
-        $data = <STDIN>;
+        while (my $newline = <STDIN>) {
+            $data .= $newline;
+            alarm $wait_time; # prevent accidental tie ups of backend processes
+        }
         alarm 0;
     };
 
@@ -400,6 +417,44 @@ sub process_request {   # The core Net::Server method
     clear_auth_token(); # logout
 }
 
+sub standalone_process_request {   # The command line version
+    my $file = shift;
+    
+    $logger->info("stream parser received file processing request for $file");
+
+    my $ph = OpenSRF::Transport::PeerHandle->retrieve;
+    if(!$ph->flush_socket()) {
+        $logger->error("We received a request, bu we are no longer connected to opensrf.  ".
+            "Exiting and dropping request for $file");
+        exit;
+    }
+
+    my ($imported, $failed) = (0, 0);
+
+    new_auth_token(); # login
+
+    if ($real_opts->{noqueue}) {
+        ($imported, $failed) = old_process_batch_data($file, 1);
+    } else {
+        ($imported, $failed) = process_batch_data($file, 1);
+    }
+
+    my $profile = (!$merge_profile) ? '' :
+        $apputils->simplereq(
+            'open-ils.pcrud', 
+            'open-ils.pcrud.retrieve.vmp', 
+            $authtoken, 
+            $merge_profile)->name;
+
+    my $msg = '';
+    $msg .= "Successfully imported $imported records using merge profile '$profile'\n" if $imported;
+    $msg .= "Failed to import $failed records\n" if $failed;
+    $msg .= "\x00";
+    print $msg;
+
+    clear_auth_token(); # logout
+}
+
 
 # the authtoken will timeout after the configured inactivity period.
 # When that happens, get a new one.
@@ -420,8 +475,16 @@ sub clear_auth_token {
 ##### MAIN ######
 
 osrf_connect($osrf_config);
-print "Calling Net::Server run ", (@ARGV ? "with command-line options: " . join(' ', @ARGV) : ''), "\n";
-__PACKAGE__->run(conf_file => $conf_file);
+if ($real_opts->{nodaemon}) {
+    if (!$real_opts->{spoolfile}) {
+        print " --nodaemon mode requested, but no --spoolfile supplied!\n";
+        exit;
+    }
+    standalone_process_request($real_opts->{spoolfile});
+} else {
+    print "Calling Net::Server run ", (@ARGV ? "with command-line options: " . join(' ', @ARGV) : ''), "\n";
+    __PACKAGE__->run(conf_file => $conf_file);
+}
 
 __END__
 
@@ -442,7 +505,7 @@ underlying L<Net::Server> options.
 
 Note: this script has to be run in the same directory as B<oils_header.pl>.
 
-Typical execution will include a trailing C<&> to run in the background.
+Typical server-style execution will include a trailing C<&> to run in the background.
 
 =head1 DESCRIPTION
 
@@ -459,6 +522,8 @@ The only required option is --password
  --tempdir          =</temp/dir/>   default: from L<opensrf.conf> <open-ils.vandelay/app_settings/databases/importer>
  --source           =<i>            default: 1
  --import-by-queue  =<i>            default: 0
+ --spoolfile        =<import_file>  default: NONE      File to import in --nodaemon mode
+ --nodaemon                         default: OFF       When used with --spoolfile, turns off Net::Server mode and runs this utility in the foreground
 
 
 =head2 Old style: --noqueue and associated options
@@ -507,6 +572,10 @@ or via the command line to restrict access as necessary.
     admin open-ils connexion --port 5555 --min_servers 2 \
     --max_servers=20 --log_file=/openils/var/log/marc_net_importer.log &
 
+./marc_stream_importer.pl  \
+    admin open-ils connexion --port 5555 --min_servers 2 \
+    --max_servers=20 --log_file=/openils/var/log/marc_net_importer.log &
+
 =head1 SEE ALSO
 
 L<Net::Server::PreFork>, L<marc_stream_importer.conf>
@@ -515,5 +584,6 @@ L<Net::Server::PreFork>, L<marc_stream_importer.conf>
 
     Bill Erickson <erickson@esilibrary.com>
     Joe Atzberger <jatzberger@esilibrary.com>
+    Mike Rylander <miker@esilibrary.com> (nodaemon+spoolfile mode)
 
 =cut
index a6311b7..897b1ea 100644 (file)
@@ -295,7 +295,6 @@ function advBuildSearchBlob() {
                                break;
                }
                if(string) {
-                       string = string.replace(/'/g,' ');
                        string = string.replace(/\\/g,' ');
             string = string.replace(/^\s*/,'');
             string = string.replace(/\s*$/,'');
index 992c9a0..c4c623a 100644 (file)
@@ -251,7 +251,19 @@ function cpdDrawCopy(r) {
         return;
     }
 
-       var b = $n(row, 'barcode').appendChild(text(copy.barcode()));
+    // Make barcode more useful for staff client usage
+    if(isXUL()) {
+        var my_a = document.createElement('a');
+        my_a.appendChild(text(copy.barcode()));
+        my_a.setAttribute("href","javascript:void(0);");
+        my_a.onclick = function() {
+            xulG.new_tab(xulG.urls.XUL_COPY_STATUS, {}, {'from_item_details_new': true, 'barcodes': [copy.barcode()]});
+               };
+        $n(row, 'barcode').appendChild(my_a);
+    }
+    else {
+       $n(row, 'barcode').appendChild(text(copy.barcode()));
+    }
 
     /* show the peer type*/
     if (pt) {
index 4810959..3a459a2 100644 (file)
                                 } else {
                                     $w('copy_summary_callnumber',cn.label());    
                                 }
-                                g.list.append({'row':{'my':{'acp':copy,'acn':cn}}});
+                                g.list.append({'row':{'my':{'acp':copy,'acn':cn,'circ':xulG.circ}}});
                                 g.barcode = copy.barcode(); g.doc_id = cn.record();
                                 if (g.doc_id > -1) {
                                     $('show_in_opac').hidden = false;
index 8ad14be..56627e0 100644 (file)
@@ -91,7 +91,7 @@ function retrieve_circ() {
                 var copy_summary = document.createElement('iframe'); csb.appendChild(copy_summary);
                 copy_summary.setAttribute('src',urls.XUL_COPY_SUMMARY); // + '?copy_id=' + r_circ.target_copy());
                 copy_summary.setAttribute('flex','1');
-                get_contentWindow(copy_summary).xulG = { 'copy_id' : r_circ.target_copy(), 'new_tab' : xulG.new_tab, 'url_prefix' : xulG.url_prefix };
+                get_contentWindow(copy_summary).xulG = { 'circ' : r_circ, 'copy_id' : r_circ.target_copy(), 'new_tab' : xulG.new_tab, 'url_prefix' : xulG.url_prefix };
 
                 g.network.simple_request(
                     'MODS_SLIM_RECORD_RETRIEVE_VIA_COPY.authoritative',
diff --git a/README b/README
index 8644216..4c943cd 100644 (file)
--- a/README
+++ b/README
@@ -1,57 +1,86 @@
 README for Evergreen trunk
+========================
 
 Installing prerequisites:
-========================
+-------------------------
+
 Evergreen has a number of prerequisite packages that must be installed
 before you can successfully configure, compile, and install Evergreen.
 
-1. Begin by installing the most recent version of OpenSRF (1.6.2 or later).
-You can download OpenSRF releases from
-http://evergreen-ils.org/downloads
-
-2. On Debian and Ubuntu, the easiest way to install the rest of the
-prerequisites for Evergreen is to use the Makefile.install prerequisite
-installer.
-
-Issue the following commands as the root user to install prerequisites
-using the Makefile.install prerequisite installer, substituting
-"debian-etch", "debian-lenny", "fedora-14", "ubuntu-hardy", "ubuntu-lucid",
-"centos", or "rhel" for <osname> below:
-
+1. Begin by installing the most recent version of OpenSRF (2.0 or later).
+   You can download OpenSRF releases from http://evergreen-ils.org/downloads
+2. On many distributions, it is necessary to install Postgres 9 from external
+   repositories.
++
+  * On Debian Squeeze, add the following line to `/etc/apt/sources.list`:
++
+[source, bash]
+------------------------------------------------------------------------------
+deb http://www.backports.org/debian squeeze-backports main contrib
+------------------------------------------------------------------------------
++
+  * On Ubuntu Lucid, add the following line to `/etc/apt/sources.list`:
++
+[source, bash]
+------------------------------------------------------------------------------
+deb http://archive.ubuntu.com/ubuntu lucid-backports main universe multiverse restricted
+------------------------------------------------------------------------------
++
+  * On Fedora 14, follow the http://yum.pgrpms.org/howtoyum.php[instructions
+    in the Yum HOWTO] to enable the PostgreSQL RPM Building Project yum
+    repository.
++
+3. On Debian and Ubuntu, the easiest way to install the rest of the
+   prerequisites for Evergreen is to use the Makefile.install prerequisite
+   installer.
+4. Issue the following commands as the root user to install prerequisites
+   using the Makefile.install prerequisite installer, substituting
+   `debian-squeeze`, `fedora-14`, `ubuntu-lucid`, `centos`, or `rhel` for
+   <osname> below:
++
+[source, bash]
+------------------------------------------------------------------------------
 make -f Open-ILS/src/extras/Makefile.install <osname>
+------------------------------------------------------------------------------
 
-Note: "centos" and "rhel" are less tested than the debian, fedora,
-and ubuntu options.  Your patches and suggestions for improvement are
+Note: `centos` and `rhel` are less tested than the `debian`, `fedora`,
+and `ubuntu` options.  Your patches and suggestions for improvement are
 welcome!
 
 Configuration and compilation instructions:
-==========================================
+-------------------------------------------
 
-For the time being, we are still installing everything in the /openils/
+For the time being, we are still installing everything in the `/openils/`
 directory. If you are working with a version of Evergreen taken directly
 from the Subversion repository, rather than a packaged version of Evergreen,
-first see "Developer instructions" below.
+first see `Developer instructions` below.
 
 Otherwise, issue the following commands to configure and build Evergreen:
 
+[source, bash]
+------------------------------------------------------------------------------
 ./configure --prefix=/openils --sysconfdir=/openils/conf
 make
+------------------------------------------------------------------------------
 
 Installation instructions:
-=========================
+--------------------------
 
 Once you have configured and compiled Evergreen, issue the following
 command as the root user to install Evergreen:
 
+[source, bash]
+------------------------------------------------------------------------------
 make STAFF_CLIENT_STAMP_ID=rel_trunk install
+------------------------------------------------------------------------------
 
 This will install Evergreen, including example configuration files in
-/openils/conf/ that you can use as templates for your own configuration files.
-The STAFF_CLIENT_STAMP_ID variable stamps the server-side and client-side files
+`/openils/conf/` that you can use as templates for your own configuration files.
+The `STAFF_CLIENT_STAMP_ID` variable stamps the server-side and client-side files
 for the staff client to ensure that they match.
 
 Install Dojo Toolkit:
-====================
+---------------------
 
 Evergreen uses the Dojo Toolkit to support its Web and staff client interfaces.
 
@@ -62,68 +91,82 @@ Issue the following commands as the root user to fetch, extract, and copy the
 files into the correct directory, adjusting the version number to match the
 version of the Dojo Toolkit that you downloaded:
 
+[source, bash]
+------------------------------------------------------------------------------
 wget http://download.dojotoolkit.org/release-1.3.3/dojo-release-1.3.3.tar.gz
 tar -C /openils/var/web/js -xzf dojo-release-1.3.3.tar.gz
 cp -r /openils/var/web/js/dojo-release-1.3.3/* /openils/var/web/js/dojo/.
+------------------------------------------------------------------------------
 
 Create the oils_web.xml configuration file:
-==========================================
+-------------------------------------------
 Many administration interfaces, such as acquisitions, bookings, and various
 configuration screens, depend on the correct configuration of HTML templates.
 Copying the sample configuration file into place should work in most cases:
 
+[source, bash]
+------------------------------------------------------------------------------
 cp /openils/conf/oils_web.xml.example /openils/conf/oils_web.xml
+------------------------------------------------------------------------------
 
 Change ownership of the Evergreen files:
-=======================================
+----------------------------------------
 
-All files in the /openils/ directory and subdirectories must be owned by the
-"opensrf" user. Issue the following command as the root user to change the
+All files in the `/openils/` directory and subdirectories must be owned by the
+`opensrf` user. Issue the following command as the root user to change the
 ownership on the files:
 
+[source, bash]
+------------------------------------------------------------------------------
 chown -R opensrf:opensrf /openils
+------------------------------------------------------------------------------
 
 Configure the Apache Web server:
-===============================
+--------------------------------
 
-Use the example configuration files in Open-ILS/examples/apache/ to configure
+Use the example configuration files in `Open-ILS/examples/apache/` to configure
 your Web server for the Evergreen catalog, staff client, Web services, and
 administration interfaces.
 
 Configure OpenSRF for the Evergreen application:
-===============================================
+------------------------------------------------
 
-There are a number of example OpenSRF configuration files in /openils/conf/ that
-you can use as a template for your Evergreen installation.
+There are a number of example OpenSRF configuration files in `/openils/conf/`
+that you can use as a template for your Evergreen installation.
 
+[source, bash]
+------------------------------------------------------------------------------
 cp /openils/conf/opensrf_core.xml.example /openils/conf/opensrf_core.xml
 cp /openils/conf/opensrf.xml.example /openils/conf/opensrf.xml
+------------------------------------------------------------------------------
 
 When you installed OpenSRF, you will have created four Jabber users on two
-separate domains and edited the opensrf_core.xml file accordingly. Please
+separate domains and edited the `opensrf_core.xml` file accordingly. Please
 refer back to the OpenSRF README and edit the Evergreen version of the
-opensrf_core.xml file using the same Jabber users and domains as you used
+`opensrf_core.xml` file using the same Jabber users and domains as you used
 while installing and testing OpenSRF.
 
-eg_db_config.pl, described in the following section, will set the database
-connection information in opensrf.xml for you.
+`eg_db_config.pl`, described in the following section, will set the database
+connection information in `opensrf.xml` for you.
 
 Creating the Evergreen database:
-===============================
+--------------------------------
 
-PostgreSQL 9.0 will be installed on your system by the Makefile.install
+PostgreSQL 9.0 will be installed on your system by the `Makefile.install`
 prerequisite installer if packages are available for your distribution, or
 you will have to compile PostgreSQL 9.0 from source and install it (which
 is beyond the scope of this document).
 
 Once the PostgreSQL database server has been installed, you will need to
 create the database and add the appropriate languages and extensions to
-support Evergreen. Issue the following commands as the "postgres" user to set
-up a database called "evergreen". Note that the location of the PostgreSQL
-"contrib" packages may vary depending on your distribution. In the following
+support Evergreen. Issue the following commands as the `postgres` user to set
+up a database called `evergreen`. Note that the location of the PostgreSQL
+`contrib` packages may vary depending on your distribution. In the following
 commands, we assume that you are working with PostgreSQL 9.0 on a Debian-based
 system:
 
+[source, bash]
+------------------------------------------------------------------------------
 createdb --template template0 --lc-ctype=C --lc-collate=C --encoding UNICODE evergreen
 createlang plperl evergreen
 createlang plperlu evergreen
@@ -132,13 +175,17 @@ psql -f /usr/share/postgresql/9.0/contrib/tablefunc.sql -d evergreen
 psql -f /usr/share/postgresql/9.0/contrib/tsearch2.sql -d evergreen
 psql -f /usr/share/postgresql/9.0/contrib/pgxml.sql -d evergreen
 psql -f /usr/share/postgresql/9.0/contrib/hstore.sql -d evergreen
+------------------------------------------------------------------------------
 
 Once you have created the Evergreen database, you need to create a PostgreSQL
-user to access the database. Issue the following command as the "postgres"
-user to create a new PostgreSQL user named "evergreen". When prompted, enter
-the new user's password and answer "yes" to make the new role a superuser:
+user to access the database. Issue the following command as the `postgres`
+user to create a new PostgreSQL user named `evergreen`. When prompted, enter
+the new user's password and answer `yes` to make the new role a superuser:
 
+[source, bash]
+------------------------------------------------------------------------------
 createuser -P evergreen
+------------------------------------------------------------------------------
 
 Once you have created the Evergreen database, you also need to create the
 database schema and configure your configuration files to point at the
@@ -148,23 +195,26 @@ with the appropriate values for your PostgreSQL database, and <admin-user> and
 <admin-pass> with the values you want for the default Evergreen administrator
 account:
 
+[source, bash]
+------------------------------------------------------------------------------
 perl Open-ILS/src/support-scripts/eg_db_config.pl --update-config \
        --service all --create-schema --create-offline \
        --user <user> --password <password> --hostname <hostname> --port <port> \
        --database <dbname> --admin-user <admin-user> --admin-pass <admin-pass>
+------------------------------------------------------------------------------
 
 This will create the database schema and configure all of the services in
-your /openils/conf/opensrf.xml configuration file to point to that database.
+your `/openils/conf/opensrf.xml` configuration file to point to that database.
 It also creates the configuration files required by the Evergreen cgi-bin
 administration scripts, and set the user name and password for the default
 Evergreen administrator account to your requested values.
 
 Developer instructions:
-======================
+-----------------------
 
 Developers working directly with the source code from the Subversion
 repository will also need to install some extra packages and perform
-one more step before they can proceed with the "./configure" step.
+one more step before they can proceed with the `./configure` step.
 
 Install the following packages:
   * autoconf
@@ -174,19 +224,25 @@ Install the following packages:
 Run the following command in the source directory to generate the configure
 script and Makefiles:
 
+[source, bash]
+------------------------------------------------------------------------------
 ./autogen.sh 
+------------------------------------------------------------------------------
 
-After running 'make install', developers also need to install the Dojo Toolkit
+After running `make install`, developers also need to install the Dojo Toolkit
 set of JavaScript libraries. The appropriate version of Dojo is included
 in Evergreen release tarballs; developers should install the Dojo 1.3.3
 version of Dojo as follows:
 
+[source, bash]
+------------------------------------------------------------------------------
 wget http://download.dojotoolkit.org/release-1.3.3/dojo-release-1.3.3.tar.gz
 tar xzf dojo-release-1.3.3.tar.gz
 cp -r dojo-release-1.3.3/* /openils/var/web/js/dojo/.
+------------------------------------------------------------------------------
 
 Getting help:
-============
+-------------
 
 Need help installing or using Evergreen? Join the mailing lists at
 http://evergreen-ils.org/listserv.php or contact us on the Freenode
index 8131258..dd0b098 100644 (file)
@@ -378,6 +378,7 @@ AC_CONFIG_FILES([Makefile
          Open-ILS/src/extras/import/marc2bre.pl
          Open-ILS/src/extras/import/marc2sre.pl
          Open-ILS/src/extras/import/parallel_pg_loader.pl
+         Open-ILS/src/support-scripts/authority_control_fields.pl
          Open-ILS/src/support-scripts/marc_export
          Open-ILS/src/perlmods/Makefile
          Open-ILS/src/perlmods/lib/OpenILS/Utils/Cronscript.pm],
@@ -388,6 +389,7 @@ AC_CONFIG_FILES([Makefile
             if test -e "./Open-ILS/src/extras/import/marc2bre.pl"; then chmod 755 Open-ILS/src/extras/import/marc2bre.pl; fi;
             if test -e "./Open-ILS/src/extras/import/marc2sre.pl"; then chmod 755 Open-ILS/src/extras/import/marc2sre.pl; fi;
             if test -e "./Open-ILS/src/extras/import/parallel_pg_loader.pl"; then chmod 755 Open-ILS/src/extras/import/parallel_pg_loader.pl; fi;
+            if test -e "./Open-ILS/src/support-scripts/authority_control_fields.pl"; then chmod 755 Open-ILS/src/support-scripts/authority_control_fields.pl; fi;
             if test -e "./Open-ILS/src/support-scripts/marc_export"; then chmod 755 Open-ILS/src/support-scripts/marc_export; fi;
         ])
 AC_OUTPUT