@@ -, +, @@ * Can only sort by title. Should be easy to fix. * Requires refetching results when changing pages. --- C4/Breeding.pm | 105 +++++++++++- cataloguing/editor.pl | 3 +- .../lib/koha/cateditor/preferences.js | 3 +- .../intranet-tmpl/lib/koha/cateditor/search.js | 148 ++++++++--------- koha-tmpl/intranet-tmpl/prog/en/css/cateditor.css | 2 +- .../prog/en/includes/cateditor-ui.inc | 179 +++++++++++++-------- .../prog/en/modules/cataloguing/editor.tt | 47 +++--- svc/z3950 | 102 ++++++++++++ 8 files changed, 422 insertions(+), 167 deletions(-) create mode 100755 svc/z3950 --- a/C4/Breeding.pm +++ a/C4/Breeding.pm @@ -30,6 +30,7 @@ use C4::AuthoritiesMarc; #GuessAuthTypeCode, FindDuplicateAuthority use C4::Languages; use Koha::Database; use Koha::XSLT_Handler; +use Time::HiRes qw( clock_gettime CLOCK_MONOTONIC ); use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); @@ -38,7 +39,7 @@ BEGIN { $VERSION = 3.07.00.049; require Exporter; @ISA = qw(Exporter); - @EXPORT = qw(&BreedingSearch &Z3950Search &Z3950SearchAuth); + @EXPORT = qw(&BreedingSearch &Z3950Search &Z3950SearchAuth &RunZ3950); } =head1 NAME @@ -326,8 +327,10 @@ sub _add_rowdata { my ($row, $record)=@_; my %fetch= ( title => 'biblio.title', + seriestitle => 'biblio.seriestitle', author => 'biblio.author', isbn =>'biblioitems.isbn', + issn =>'biblioitems.issn', lccn =>'biblioitems.lccn', #LC control number (not call number) edition =>'biblioitems.editionstatement', date => 'biblio.copyrightdate', #MARC21 @@ -711,6 +714,106 @@ sub Z3950SearchAuth { ); } +sub RunZ3950 { + my ( $server_ids, $query, $options ) = @_; + + $options = { + offset => 0, + fetch => 20, + on_error => sub {}, + on_hit => sub {}, + %{ $options || {} } + }; + + my $schema = Koha::Database->new->schema; + my $stats = { + num_fetched => { + map { $_ => 0 } @$server_ids + }, + num_hits => { + map { $_ => 0 } @$server_ids + }, + total_fetched => 0, + total_hits => 0, + }; + my $start = clock_gettime( CLOCK_MONOTONIC ); + my @servers; + + foreach my $server ( $schema->resultset('Z3950server')->search( { id => $server_ids } )->all ) { + my $zoptions = ZOOM::Options->new(); + $zoptions->option( 'async', 1 ); + $zoptions->option( 'elementSetName', 'F' ); + $zoptions->option( 'databaseName', $server->db ); + $zoptions->option( 'user', $server->userid ) if $server->userid; + $zoptions->option( 'password', $server->password ) if $server->password; + $zoptions->option( 'preferredRecordSyntax', $server->syntax ); + $zoptions->option( 'timeout', $server->timeout ) if $server->timeout; + + my $connection = ZOOM::Connection->create($zoptions); + $connection->connect( $server->host, $server->port ); + + push @servers, { + connection => $connection, + id => $server->id, + host => $server->host, + name => $server->name, + encoding => ( $server->encoding ? $server->encoding : "iso-5426" ), + results => $connection->search_pqf( $query ), # Starts the search + }; + } + + my $servers_left = scalar @servers; + my $total_raw_size = 0; + + while ( $servers_left ) { + my $i; + + # Read pending events from servers until one finishes + while ( ( $i = ZOOM::event( [ map { $_->{connection} } @servers ] ) ) != 0 ) { + last if $servers[ $i - 1 ]->{connection}->last_event() == ZOOM::Event::ZEND; + } + + $servers_left--; + my $server = $servers[ --$i ]; + my $exception = $server->{connection}->exception(); #ignores errmsg, addinfo, diagset + + if ($exception) { + $options->{on_error}->( $server, $exception ); + } else { + my $num_results = $stats->{num_hits}->{ $server->{id} } = $server->{results}->size; + my $num_fetched = $stats->{num_fetched}->{ $server->{id} } = ( $options->{offset} + $options->{fetch} ) < $num_results ? $options->{fetch} : $num_results; + + $stats->{total_hits} += $num_results; + $stats->{total_fetched} += $num_fetched; + + next if ( !$num_results ); + + my $hits = $server->{results}->records( $options->{offset}, $num_fetched, 1 ); + + if ( !@$hits ) { + $options->{on_error}->( $server, $server->{connection}->exception() ) if ( $server->{connection}->exception() ); + next; + } + + foreach my $j ( 0..$#$hits ) { + $total_raw_size += length $hits->[$j]->raw(); + my ($marcrecord) = MarcToUTF8Record( $hits->[$j]->raw(), C4::Context->preference('marcflavour'), $server->{encoding} ); #ignores charset return values + my $metadata = {}; + _add_rowdata( $metadata, $marcrecord ); + $options->{on_hit}->( $server, { + index => $options->{offset} + $j, + record => $marcrecord, + metadata => $metadata, + } ); + } + } + } + + $stats->{time} = clock_gettime( CLOCK_MONOTONIC ) - $start; + + return $stats; +} + 1; __END__ --- a/cataloguing/editor.pl +++ a/cataloguing/editor.pl @@ -45,8 +45,9 @@ $template->{VARS}->{DefaultLanguageField008} = pack( 'A3', C4::Context->preferen # Z39.50 servers my $dbh = C4::Context->dbh; -$template->{VARS}->{z3950_targets} = $dbh->selectall_arrayref( q{ +$template->{VARS}->{z3950_servers} = $dbh->selectall_arrayref( q{ SELECT * FROM z3950servers + WHERE recordtype != 'authority' ORDER BY name }, { Slice => {} } ); --- a/koha-tmpl/intranet-tmpl/lib/koha/cateditor/preferences.js +++ a/koha-tmpl/intranet-tmpl/lib/koha/cateditor/preferences.js @@ -1,7 +1,8 @@ define( function() { var Preferences = { Load: function( borrowernumber ) { - if ( !borrowernumber ) return; + if ( borrowernumber == null ) return; + var saved_prefs; try { saved_prefs = JSON.parse( localStorage[ 'cateditor_preferences_' + borrowernumber ] ); --- a/koha-tmpl/intranet-tmpl/lib/koha/cateditor/search.js +++ a/koha-tmpl/intranet-tmpl/lib/koha/cateditor/search.js @@ -1,94 +1,88 @@ -define( [ 'marc-record', 'pz2' ], function( MARC, Pazpar2 ) { - //var _pz; - var _onresults; - var _recordCache = {}; +define( [ 'marc-record' ], function( MARC ) { var _options; + var _records = {}; + var _last; - var Search = { - Init: function( targets, options ) { - var initOpts = {}; + var _pqfMapping = { + author: '1=1004', // s=al', + cn_dewey: '1=13', + cn_lc: '1=16', + date: '1=30', // r=r', + isbn: '1=7', + issn: '1=8', + lccn: '1=9', + local_number: '1=12', + music_identifier: '1=51', + standard_identifier: '1=1007', + subject: '1=21', // s=al', + term: '1=1016', // t=l,r s=al', + title: '1=4', // s=al', + } - $.each( targets, function ( url, info ) { - initOpts[ 'pz:name[' + url + ']' ] = info.name; - initOpts[ 'pz:queryencoding[' + url + ']' ] = info.encoding; - initOpts[ 'pz:xslt[' + url + ']' ] = info.kohasyntax.toLowerCase() + '-work-groups.xsl'; - initOpts[ 'pz:requestsyntax[' + url + ']' ] = info.syntax; + var Search = { + Init: function( options ) { + _options = options; + }, + JoinTerms: function( terms ) { + var q = ''; - // Load in default CCL mappings - // Pazpar2 seems to have a bug where wildcard cclmaps are ignored. - // What an incredible surprise. - initOpts[ 'pz:cclmap:term[' + url + ']' ] = 'u=1016 t=l,r s=al'; - initOpts[ 'pz:cclmap:Author-name[' + url + ']' ] = 'u=1004 s=al'; - initOpts[ 'pz:cclmap:Classification-Dewey[' + url + ']' ] = 'u=13'; - initOpts[ 'pz:cclmap:Classification-LC[' + url + ']' ] = 'u=16'; - initOpts[ 'pz:cclmap:Date[' + url + ']' ] = 'u=30 r=r'; - initOpts[ 'pz:cclmap:Identifier-ISBN[' + url + ']' ] = 'u=7'; - initOpts[ 'pz:cclmap:Identifier-ISSN[' + url + ']' ] = 'u=8'; - initOpts[ 'pz:cclmap:Identifier-publisher-for-music[' + url + ']' ] = 'u=51'; - initOpts[ 'pz:cclmap:Identifier-standard[' + url + ']' ] = 'u=1007'; - initOpts[ 'pz:cclmap:LC-card-number[' + url + ']' ] = 'u=9'; - initOpts[ 'pz:cclmap:Local-number[' + url + ']' ] = 'u=12'; - initOpts[ 'pz:cclmap:Subject[' + url + ']' ] = 'u=21 s=al'; - initOpts[ 'pz:cclmap:Title[' + url + ']' ] = 'u=4 s=al'; + $.each( terms, function( i, term ) { + var term = '@attr ' + _pqfMapping[ term[0] ] + ' "' + term[1].replace( '"', '\\"' ) + '"' - if ( info.authentication ) initOpts[ 'pz:authentication[' + url + ']' ] = info.authentication; + if ( q ) { + q = '@and ' + q + ' ' + term; + } else { + q = term; + } } ); - _options = $.extend( { - initopts: initOpts, - onshow: Search._onshow, - errorhandler: Search._onerror, - }, options ); - - _pz = new Pazpar2( _options ); + return q; }, - Reconnect: function() { - _pz.reset(); - _pz = new Pazpar2( _options ); - }, - Start: function( targets, q, limit ) { - Search.includedTargets = []; - recordcache = {}; + Run: function( servers, q, options ) { + Search.includedServers = []; + _records = {}; + _last = { + servers: servers, + q: q, + options: options, + }; - $.each( targets, function ( url, info ) { - if ( !info.disabled ) Search.includedTargets.push( url ); - } ); + options = $.extend( { + offset: 0, + page_size: 20, + }, _options, options ); - _pz.search( q, limit, 'relevance:0', 'pz:id=' + Search.includedTargets.join( '|' ) ); - return true; - }, - Fetch: function( offset ) { - _pz.show( offset ); - }, - GetDetailedRecord: function( recid, callback ) { - if ( _recordCache[recid] ) { - callback( _recordCache[recid] ); - return; - } + $.each( servers, function ( id, info ) { + if ( info.checked ) Search.includedServers.push( id ); + } ); - _pz.record( recid, 0, undefined, { callback: function(data) { - var record = _recordCache[recid] = new MARC.Record(); - record.loadMARCXML(data.xmlDoc); + $.get( + '/cgi-bin/koha/svc/z3950', + { + q: q, + servers: Search.includedServers.join( ',' ), + offset: options.offset, + page_size: options.page_size + } + ) + .done( function( data ) { + $.each( data.hits, function( undef, hit ) { + var record = new MARC.Record(); + record.loadMARCXML( hit.record ); + hit.record = record; + } ); - callback(record); - } } ); - }, - IsAvailable: function() { - return _pz.initStatusOK; - }, - _onshow: function( data ) { - $.each( data.hits, function( undef, hit ) { - hit.id = 'search:' + encodeURIComponent( hit.recid[0] ); - } ); + _options.onresults( data ); + } ) + .fail( function( error ) { + _options.onerror( error ); + } ); - _options.onresults( data ); + return true; }, - _onerror: function( error ) { - if ( _options.oniniterror && !_pz.initStatusOK ) { - _options.oniniterror( error ); - } else { - _options.onerror( error ); - } + Fetch: function( offset ) { + if ( !_last ) return; + Search.Run( _last.servers, _last.q, $.extend( {}, _last.options, { offset: offset } ) ); } }; --- a/koha-tmpl/intranet-tmpl/prog/en/css/cateditor.css +++ a/koha-tmpl/intranet-tmpl/prog/en/css/cateditor.css @@ -315,7 +315,7 @@ body { line-height: 24px; } -.results-marc { +.marccol { font-family: monospace; height: auto; white-space: pre-wrap; --- a/koha-tmpl/intranet-tmpl/prog/en/includes/cateditor-ui.inc +++ a/koha-tmpl/intranet-tmpl/prog/en/includes/cateditor-ui.inc @@ -10,12 +10,6 @@ require.config( { themelang: '[% themelang %]', }, }, - paths: { - pz2: '../../pz2', - }, - shim: { - pz2: { exports: 'pz2' }, - }, } ); @@ -27,30 +21,27 @@ require.config( {