initial overdrive import code
authorJames Fournie <jfournie@sitka.bclibraries.ca>
Tue, 25 Oct 2011 23:04:55 +0000 (16:04 -0700)
committerJames Fournie <jfournie@sitka.bclibraries.ca>
Tue, 25 Oct 2011 23:04:55 +0000 (16:04 -0700)
marc_import_overdrive/overdrive-import.pl [new file with mode: 0755]
marc_import_overdrive/overdrive-ingest-db-func.sql [new file with mode: 0755]

diff --git a/marc_import_overdrive/overdrive-import.pl b/marc_import_overdrive/overdrive-import.pl
new file mode 100755 (executable)
index 0000000..563a193
--- /dev/null
@@ -0,0 +1,256 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use lib '/openils/lib/perl5/';
+
+use Error qw/:try/;
+use OpenILS::Utils::Fieldmapper;
+use Digest::MD5 qw/md5_hex/;
+use OpenSRF::Utils::JSON;
+use OpenILS::Application::AppUtils;
+use Data::Dumper;
+use Unicode::Normalize;
+use Encode;
+
+use FileHandle;
+use Time::HiRes qw/time/;
+use Getopt::Long;
+use MARC::Batch;
+use MARC::File::XML ( BinaryEncoding => 'utf-8' );
+use MARC::Charset;
+use DBI;
+
+#MARC::Charset->ignore_errors(1);
+
+my ($config, $idlfile, $marctype, $enc) =
+       ('/srv/openils/conf/opensrf_core.xml', '/srv/openils/conf/fm_IDL.xml', 'USMARC', 'utf8');
+
+my (@files, @trash_fields, @req_fields, $quiet, $startid);
+
+my @targetorg = ('BPR','BFN','BTE');
+
+@req_fields = ('856');
+my $overdrive_prefix = 'http\:\/\/downloads\.bclibrary\.ca\/ContentDetails\.htm\?ID\=';
+my $tcn_prefix = "LtG_";
+
+GetOptions(
+       'marctype=s'    => \$marctype, # format of MARC files being processed defaults to USMARC, often set to XML
+       'encoding=s'    => \$enc, # set assumed MARC encoding for MARC::Charset
+       'config=s'      => \$config, # location of OpenSRF core config file, defaults to /openils/conf/opensrf_core.xml
+       'file=s'        => \@files, # files to process (or you can simple list the files as unnamed arguments, i.e. @ARGV)
+       'required_fields=s'     => \@req_fields, # skip any records missing these fields
+       'trash=s'       => \@trash_fields, # fields to remove from all processed records
+       'xml_idl=s'     => \$idlfile, # location of XML IDL file, defaults to /openils/conf/fm_IDL.xml
+       'startid=i'     => \$startid, #starting ID
+       'quiet'         => \$quiet # do not output progress count
+);
+
+@trash_fields = split(/,/,join(',',@trash_fields));
+@req_fields = split(/,/,join(',',@req_fields));
+
+if ($enc) {
+       MARC::Charset->ignore_errors(1);
+       MARC::Charset->assume_encoding($enc);
+}
+
+if (uc($marctype) eq 'XML') {
+       'open'->use(':utf8');
+} else {
+       bytes->use();
+}
+
+@files = @ARGV if (!@files);
+
+Fieldmapper->import(IDL => $idlfile);
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+my $batch = new MARC::Batch ( $marctype, @files );
+$batch->strict_off();
+$batch->warnings_off();
+
+my $starttime = time;
+my $rec;
+my $count = 0;
+
+my $id = 1 || $startid;
+
+PROCESS: while ( try { $rec = $batch->next } otherwise { $rec = -1 } ) {
+       next if ($rec == -1);
+    print STDERR "======\n";
+    $id++;
+       $count++;
+
+       # Skip records that don't contain a required field (like '245', for example)
+       foreach my $req_field (@req_fields) {
+               if (!$rec->field("$req_field")) {
+                       warn "\n!!! Record $count missing required field $req_field, skipping record.\n";
+                       next PROCESS;
+               }
+       }
+
+# -----------------
+# Overdrive - specific code
+# -----------------
+
+    my $tcn_value; 
+       my $tcn_source = 'Library To Go';
+
+    my $caption = 'DOWNLOADABLE AUDIOBOOK';
+
+    # this is the base 856 field we're going to generate separate fields for each org unit we're scoping at
+    my $baseurifield;
+
+    # check all 856s
+    URIFIELD: foreach my $uri ($rec->field('856')){
+
+        # Overdrive uses a $3 for Excerpts, we want to keep this intact so carry on then
+        next URIFIELD if ($uri->subfield('3')); 
+
+        # we need a $u for a URL, if we don't have this it is bad
+        my $url = $uri->subfield('u');
+        if(!$url){
+            warn "856 has no URL in rec $id.  Skipping";
+        } 
+
+        # this record has been through Evergreen if a $9 exists somewhere
+        # instead, we can presumably pull the tcn from the 901 and generate our base field that way
+        if($uri->subfield('9')){
+            # if this doesn't match our prefix, ignore it, we only care about our current prefix
+            next unless($url =~ m/($overdrive_prefix)/);
+
+            # if it does:
+            # delete $9 subfield and use this as a base uri field
+            $baseurifield = $uri->clone;
+            $rec->delete_fields($uri);
+            next URIFIELD;
+        }
+
+
+        if($uri->subfield('z')){
+            $caption = 'EBOOK' if ($uri->subfield('z') =~ /Book/);
+            $uri->delete_subfield(code => 'z');
+            $uri->add_subfields('z' => 'Click to access online (library card required)');
+
+            next unless($url =~ m/($overdrive_prefix)/);
+
+            # trim out Overdrive's magical GUID-looking ID thingy
+            my $overdrivekey = $url;
+            $overdrivekey =~ s/($overdrive_prefix)//g;
+
+            # make it TCN-ish
+               $tcn_value = $tcn_prefix . $overdrivekey;
+
+            # we have a base for our scoped fields
+            $baseurifield = $uri->clone;
+
+            $rec->delete_fields($uri);
+        }
+    }
+
+    if(!$baseurifield){
+        die "the horror!";
+    }
+    # add some arbitrary stuff as prescribed by our cataloguer overlords
+    $rec = adjust_leader($rec);
+    $rec = process_custom_fields($rec);
+
+
+    # add a scoped field for each org unit in our array
+    foreach(@targetorg){
+        if($baseurifield){
+            my $newfield = $baseurifield->clone();
+            $newfield->add_subfields('9' => $_);
+            $rec->insert_fields_ordered($newfield);
+        } else {
+            die;# $rec->as_formatted();
+        }
+    }
+
+# -----------------
+# END Overdrive - specific code
+# -----------------
+
+
+       $rec->delete_field($_) for ($rec->field(@trash_fields));
+
+    my $field901 = MARC::Field->new(
+            '901' => ('', ''),
+            a => $tcn_value,
+            b => $tcn_source,
+            c => $id,
+    );
+
+
+    $rec->insert_fields_ordered($field901);
+
+    print $rec->as_formatted();
+
+    next PROCESS;
+
+
+       $tcn_value = $rec->subfield('901' => 'a');
+       $tcn_source = $rec->subfield('901' => 'b');
+       $id = $rec->subfield('901' => 'c');
+
+       (my $xml = $rec->as_xml_record()) =~ s/\n//sog;
+       $xml =~ s/^<\?xml.+\?\s*>//go;
+       $xml =~ s/>\s+</></go;
+       $xml =~ s/\p{Cc}//go;
+       $xml = OpenILS::Application::AppUtils->entityize($xml);
+       $xml =~ s/[\x00-\x1f]//go;
+
+       my $bib = new Fieldmapper::biblio::record_entry;
+       $bib->id($id);
+       $bib->active('t');
+       $bib->deleted('f');
+       $bib->marc($xml);
+       $bib->creator(0);
+       $bib->create_date('now');
+       $bib->editor(0);
+       $bib->edit_date('now');
+       $bib->tcn_source($tcn_source);
+       $bib->tcn_value($tcn_value);
+       $bib->last_xact_id('IMPORT-'.$starttime);
+
+       #print OpenSRF::Utils::JSON->perl2JSON($bib)."\n";
+
+       if (!$quiet){# && !($count % 50)) {
+               print STDERR "\r$count\t". $count / (time - $starttime);
+       }
+}
+
+sub adjust_leader {
+    my $rec = shift;
+    my $leader = $rec->leader();
+    $leader = substr($leader,0,5) . 'm' . substr($leader,6,length($leader));
+    $rec->leader($leader);
+    return $rec;
+}
+
+sub process_custom_fields{
+    my $rec = shift;
+    my $caption = shift;
+   
+    my @newfields;
+
+    push @newfields, MARC::Field->new(
+        '538' => (' ', ' '),
+        a => "Requires OverDrive Media Console"
+    );
+    push @newfields, MARC::Field->new(
+        '594' => (' ', ' '),
+        a => "Library To Go"
+    );
+
+    push @newfields, MARC::Field->new(
+        '655' => (' ', '4'),
+        'a' => $caption 
+    );    
+
+    $rec->insert_fields_ordered(@newfields);
+    return $rec;
+}
+
diff --git a/marc_import_overdrive/overdrive-ingest-db-func.sql b/marc_import_overdrive/overdrive-ingest-db-func.sql
new file mode 100755 (executable)
index 0000000..51cadeb
--- /dev/null
@@ -0,0 +1,96 @@
+CREATE OR REPLACE FUNCTION sitka.overdrive_MARCXML_delete_all_uri_fields( TEXT ) RETURNS TEXT AS $func$
+    use MARC::Record;
+    use MARC::File::XML (BinaryEncoding => 'UTF-8');
+
+    my $xml = shift;
+
+    my $r = MARC::Record->new_from_xml( $xml );
+
+    # check all 856s
+    URIFIELD: foreach my $uri ($r->field('856')){
+
+        # Overdrive uses a $3 for Excerpts, we want to keep this intact so carry on then
+        next URIFIELD if ($uri->subfield('3')); 
+
+        # otherwise, delete away
+        $r->delete_fields($uri);
+    }
+
+    $xml = $r->as_xml_record();
+    $xml =~ s/^<\?.+?\?>$//mo;
+    $xml =~ s/\n//sgo;
+    $xml =~ s/>\s+</></sgo;
+    return $xml;
+$func$ LANGUAGE PLPERLU;
+
+CREATE OR REPLACE FUNCTION sitka.overdrive_MARCXML_add_uri_field( TEXT, TEXT, TEXT, TEXT, TEXT ) RETURNS TEXT AS $func$
+    use MARC::Record;
+    use MARC::File::XML (BinaryEncoding => 'UTF-8');
+
+    my $xml = shift;
+       my $tcn_prefix = shift;
+       my $url_prefix = shift;
+       my $uri_caption = shift;
+    my $shortname = shift;
+
+    my $r = MARC::Record->new_from_xml( $xml );
+
+    my $tcn = $r->subfield('901','a');
+    $tcn =~ s/^$tcn_prefix//g;
+
+    foreach my $uri ($r->field('856')){
+        if($uri->subfield('9')){
+            if($uri->subfield('9') eq $shortname){
+                $r->delete_field($uri);
+            }
+        }
+    }
+    my $field856 = MARC::Field->new(
+            '856' => ('4', '0'),
+            u => $url_prefix . $tcn,
+            z => $uri_caption,
+            9 => $shortname
+    );
+
+    $r->insert_fields_ordered($field856);
+
+    $xml = $r->as_xml_record();
+    $xml =~ s/^<\?.+?\?>$//mo;
+    $xml =~ s/\n//sgo;
+    $xml =~ s/>\s+</></sgo;
+    return $xml;
+
+$func$ LANGUAGE PLPERLU;
+
+-- #u => "http://downloads.bclibrary.ca/ContentDetails.htm?ID=" . $tcn,
+-- "Click to access online (library card required)",
+
+
+CREATE OR REPLACE FUNCTION sitka.overdrive_bc_refresh_uris ( rid BIGINT ) RETURNS BOOLEAN AS $$
+DECLARE
+       marcxml TEXT;
+       tcnprefix TEXT := 'Overdrive_';
+       urlprefix TEXT := 'http://downloads.bclibrary.ca/ContentDetails.htm?ID=';
+       urlcaption TEXT := 'Click to access online (library card required)';
+BEGIN
+    EXECUTE SELECT sitka.overdrive_MARCXML_delete_all_uri_fields(marc) FROM biblio.record_entry where id = rid INTO marcxml;
+
+       -- clean out asset.call_number and asset.uri and asset.uri_call_number_map
+
+       -- could be modified to pull shortnames based on an ou setting
+       -- select sitka.overdrive_MARCXML_add_uri_field(rid,tcnprefix,urlprefix,urlcaption,a.shortname) 
+       -- from actor.org_unit_setting b join actor.org_unit a on (a.id = b.org_unit) where b.name = 'sitka.overdrive_setting' and b.value = true
+       -- something like that
+
+       EXECUTE SELECT sitka.overdrive_bc_add_uri_field (rid, tcnprefix, urlprefix, urlcaption, 'BBGVL') INTO marcxml;
+       EXECUTE SELECT sitka.overdrive_bc_add_uri_field (rid, tcnprefix, urlprefix, urlcaption, 'BBNCLF') INTO marcxml;
+       EXECUTE SELECT sitka.overdrive_bc_add_uri_field (rid, tcnprefix, urlprefix, urlcaption, 'BCK') INTO marcxml;
+       EXECUTE SELECT sitka.overdrive_bc_add_uri_field (rid, tcnprefix, urlprefix, urlcaption, 'BNCLF') INTO marcxml;
+       EXECUTE SELECT sitka.overdrive_bc_add_uri_field (rid, tcnprefix, urlprefix, urlcaption, 'BNELF') INTO marcxml;
+       EXECUTE SELECT sitka.overdrive_bc_add_uri_field (rid, tcnprefix, urlprefix, urlcaption, 'ISLANDLINK') INTO marcxml;
+       EXECUTE SELECT sitka.overdrive_bc_add_uri_field (rid, tcnprefix, urlprefix, urlcaption, 'UNFEDERATED') INTO marcxml;
+
+    EXECUTE UPDATE biblio.record_entry set marc = marcxml WHERE id = rid;
+       return true;
+END;
+$$ LANGUAGE PLPGSQL IMMUTABLE;